Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-04-08 12:09:18 +00:00
parent 9ce920f62f
commit cca8451493
28 changed files with 300 additions and 194 deletions

View File

@ -53,6 +53,8 @@ workflow:
variables:
RAILS_ENV: "test"
NODE_ENV: "test"
# we override the max_old_space_size to prevent OOM errors
NODE_OPTIONS: --max_old_space_size=3584
SIMPLECOV: "true"
GIT_DEPTH: "20"
GIT_SUBMODULE_STRATEGY: "none"

View File

@ -1,22 +1,15 @@
.frontend-base:
extends:
- .default-retry
- .default-before_script
variables:
SETUP_DB: "false"
# we override the max_old_space_size to prevent OOM errors
NODE_OPTIONS: --max_old_space_size=3584
.yarn-install: &yarn-install
- source scripts/utils.sh
- run_timed_command "retry yarn install --frozen-lockfile"
.compile-assets-base:
extends:
- .frontend-base
- .default-retry
- .default-before_script
- .assets-compile-cache
image: registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.7.2-git-2.29-lfs-2.9-node-14.15-yarn-1.22-graphicsmagick-1.3.34
variables:
SETUP_DB: "false"
WEBPACK_VENDOR_DLL: "true"
stage: prepare
script:
@ -93,13 +86,13 @@ update-yarn-cache:
.frontend-fixtures-base:
extends:
- .frontend-base
- .default-retry
- .default-before_script
- .rails-cache
- .use-pg11
stage: fixtures
needs: ["setup-test-env", "retrieve-tests-metadata", "compile-test-assets"]
variables:
SETUP_DB: "true"
WEBPACK_VENDOR_DLL: "true"
script:
- run_timed_command "gem install knapsack --no-document"
@ -151,10 +144,8 @@ graphql-schema-dump:
.frontend-test-base:
extends:
- .frontend-base
- .default-retry
- .yarn-cache
variables:
USE_BUNDLE_INSTALL: "false"
stage: test
eslint-as-if-foss:

View File

@ -44,8 +44,6 @@ memory-on-boot:
NODE_ENV: "production"
RAILS_ENV: "production"
SETUP_DB: "true"
# we override the max_old_space_size to prevent OOM errors
NODE_OPTIONS: --max_old_space_size=3584
script:
- PATH_TO_HIT="/users/sign_in" CUT_OFF=0.3 bundle exec derailed exec perf:mem >> 'tmp/memory_on_boot.txt'
- scripts/generate-memory-metrics-on-boot tmp/memory_on_boot.txt >> 'tmp/memory_on_boot_metrics.txt'

View File

@ -4,8 +4,6 @@ import { isString, mapValues, isNumber, reduce } from 'lodash';
import * as timeago from 'timeago.js';
import { languageCode, s__, __, n__ } from '../../locale';
const MILLISECONDS_IN_HOUR = 60 * 60 * 1000;
const MILLISECONDS_IN_DAY = 24 * MILLISECONDS_IN_HOUR;
const DAYS_IN_WEEK = 7;
window.timeago = timeago;
@ -946,49 +944,6 @@ export const format24HourTimeStringFromInt = (time) => {
return formatted24HourString;
};
/**
* A utility function which checks if two date ranges overlap.
*
* @param {Object} givenPeriodLeft - the first period to compare.
* @param {Object} givenPeriodRight - the second period to compare.
* @returns {Object} { daysOverlap: number of days the overlap is present, hoursOverlap: number of hours the overlap is present, overlapStartDate: the start date of the overlap in time format, overlapEndDate: the end date of the overlap in time format }
* @throws {Error} Uncaught Error: Invalid period
*
* @example
* getOverlapDateInPeriods(
* { start: new Date(2021, 0, 11), end: new Date(2021, 0, 13) },
* { start: new Date(2021, 0, 11), end: new Date(2021, 0, 14) }
* ) => { daysOverlap: 2, hoursOverlap: 48, overlapStartDate: 1610323200000, overlapEndDate: 1610496000000 }
*
*/
export const getOverlapDateInPeriods = (givenPeriodLeft = {}, givenPeriodRight = {}) => {
const leftStartTime = new Date(givenPeriodLeft.start).getTime();
const leftEndTime = new Date(givenPeriodLeft.end).getTime();
const rightStartTime = new Date(givenPeriodRight.start).getTime();
const rightEndTime = new Date(givenPeriodRight.end).getTime();
if (!(leftStartTime <= leftEndTime && rightStartTime <= rightEndTime)) {
throw new Error(__('Invalid period'));
}
const isOverlapping = leftStartTime < rightEndTime && rightStartTime < leftEndTime;
if (!isOverlapping) {
return { daysOverlap: 0 };
}
const overlapStartDate = Math.max(leftStartTime, rightStartTime);
const overlapEndDate = rightEndTime > leftEndTime ? leftEndTime : rightEndTime;
const differenceInMs = overlapEndDate - overlapStartDate;
return {
hoursOverlap: Math.ceil(differenceInMs / MILLISECONDS_IN_HOUR),
daysOverlap: Math.ceil(differenceInMs / MILLISECONDS_IN_DAY),
overlapStartDate,
overlapEndDate,
};
};
/**
* A utility function that checks that the date is today
*

View File

@ -5,10 +5,29 @@ import GpgBadges from '~/gpg_badges';
import initBlob from '~/pages/projects/init_blob';
import initWebIdeLink from '~/pages/projects/shared/web_ide_link';
import commitPipelineStatus from '~/projects/tree/components/commit_pipeline_status_component.vue';
import BlobContentViewer from '~/repository/components/blob_content_viewer.vue';
import '~/sourcegraph/load';
new BlobViewer(); // eslint-disable-line no-new
initBlob();
const viewBlobEl = document.querySelector('#js-view-blob-app');
if (viewBlobEl) {
const { blobPath } = viewBlobEl.dataset;
// eslint-disable-next-line no-new
new Vue({
el: viewBlobEl,
render(createElement) {
return createElement(BlobContentViewer, {
props: {
path: blobPath,
},
});
},
});
} else {
new BlobViewer(); // eslint-disable-line no-new
initBlob();
}
const CommitPipelineStatusEl = document.querySelector('.js-commit-pipeline-status');
const statusLink = document.querySelector('.commit-actions .ci-status-link');

View File

@ -54,11 +54,17 @@ export default {
return this.currentRequest.details[this.metric];
},
metricDetailsSummary() {
return {
[s__('Total')]: this.metricDetails.calls,
[s__('PerformanceBar|Total duration')]: this.metricDetails.duration,
...(this.metricDetails.summary || {}),
};
const summary = {};
if (!this.metricDetails.summaryOptions?.hideTotal) {
summary[s__('Total')] = this.metricDetails.calls;
}
if (!this.metricDetails.summaryOptions?.hideDuration) {
summary[s__('PerformanceBar|Total duration')] = this.metricDetails.duration;
}
return { ...summary, ...(this.metricDetails.summary || {}) };
},
metricDetailsLabel() {
if (this.metricDetails.duration && this.metricDetails.calls) {
@ -133,7 +139,7 @@ export default {
>
<gl-button v-gl-modal="modalId" class="gl-mr-2" type="button" variant="link">
<span
class="gl-text-blue-300 gl-font-weight-bold"
class="gl-text-blue-200 gl-font-weight-bold"
data-testid="performance-bar-details-label"
>
{{ metricDetailsLabel }}
@ -208,7 +214,7 @@ export default {
<div></div>
</template>
</gl-modal>
{{ title }}
<span class="gl-text-white">{{ title }}</span>
<request-warning :html-id="htmlId" :warnings="warnings" />
</div>
</template>

View File

@ -136,7 +136,7 @@ export default {
<div id="peek-view-host" class="view">
<span
v-if="hasHost"
class="current-host"
class="current-host gl-text-white"
:class="{ canary: currentRequest.details.host.canary }"
>
<span v-html="birdEmoji"></span>
@ -157,16 +157,16 @@ export default {
id="peek-view-trace"
class="view"
>
<a class="gl-text-blue-300" :href="currentRequest.details.tracing.tracing_url">{{
<a class="gl-text-blue-200" :href="currentRequest.details.tracing.tracing_url">{{
s__('PerformanceBar|Trace')
}}</a>
</div>
<div v-if="currentRequest.details" id="peek-download" class="view">
<a class="gl-text-blue-300" :download="downloadName" :href="downloadPath">{{
<a class="gl-text-blue-200" :download="downloadName" :href="downloadPath">{{
s__('PerformanceBar|Download')
}}</a>
</div>
<a v-if="statsUrl" class="gl-text-blue-300 view" :href="statsUrl">{{
<a v-if="statsUrl" class="gl-text-blue-200 view" :href="statsUrl">{{
s__('PerformanceBar|Stats')
}}</a>
<request-selector

View File

@ -1,6 +1,7 @@
/* eslint-disable @gitlab/require-i18n-strings */
import Vue from 'vue';
import axios from '~/lib/utils/axios_utils';
import { numberToHumanSize } from '~/lib/utils/number_utils';
import { s__ } from '~/locale';
import Translate from '~/vue_shared/translate';
import initPerformanceBarLog from './performance_bar_log';
@ -75,40 +76,53 @@ const initPerformanceBar = (el) => {
const resourceEntries = performance.getEntriesByType('resource');
let durationString = '';
let summary = {};
if (navigationEntries.length > 0) {
durationString = `${Math.round(navigationEntries[0].responseEnd)} | `;
durationString += `${Math.round(paintEntries[1].startTime)} | `;
durationString += ` ${Math.round(navigationEntries[0].domContentLoadedEventEnd)}`;
const backend = Math.round(navigationEntries[0].responseEnd);
const firstContentfulPaint = Math.round(paintEntries[1].startTime);
const domContentLoaded = Math.round(navigationEntries[0].domContentLoadedEventEnd);
summary = {
[s__('PerformanceBar|Backend')]: backend,
[s__('PerformanceBar|First Contentful Paint')]: firstContentfulPaint,
[s__('PerformanceBar|DOM Content Loaded')]: domContentLoaded,
};
durationString = `${backend} | ${firstContentfulPaint} | ${domContentLoaded}`;
}
let newEntries = resourceEntries.map(this.transformResourceEntry);
this.updateFrontendPerformanceMetrics(durationString, newEntries);
this.updateFrontendPerformanceMetrics(durationString, summary, 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);
this.updateFrontendPerformanceMetrics(durationString, summary, newEntries);
});
observer.observe({ entryTypes: ['resource'] });
}
}
},
updateFrontendPerformanceMetrics(durationString, requestEntries) {
updateFrontendPerformanceMetrics(durationString, summary, requestEntries) {
this.store.setRequestDetailsData(this.requestId, 'total', {
duration: durationString,
calls: requestEntries.length,
details: requestEntries,
summaryOptions: {
hideDuration: true,
},
summary,
});
},
transformResourceEntry(entry) {
const nf = new Intl.NumberFormat();
return {
start: entry.startTime,
name: entry.name.replace(document.location.origin, ''),
duration: Math.round(entry.duration),
size: entry.transferSize ? `${nf.format(entry.transferSize)} bytes` : 'cached',
size: entry.transferSize ? numberToHumanSize(entry.transferSize) : 'cached',
};
},
},

View File

@ -842,10 +842,10 @@ $linked-project-column-margin: 60px;
/*
Performance Bar
*/
$perf-bar-production: #222;
$perf-bar-staging: #291430;
$perf-bar-development: #4c1210;
$perf-bar-bucket-bg: #111;
$perf-bar-production: $gray-950;
$perf-bar-staging: $indigo-950;
$perf-bar-development: $red-950;
$perf-bar-bucket-bg: $black;
$perf-bar-bucket-box-shadow-from: rgba($white, 0.2);
$perf-bar-bucket-box-shadow-to: rgba($black, 0.25);
$perf-bar-canary-text: $orange-400;

View File

@ -14,7 +14,7 @@
color: $gray-300;
select {
color: $gray-300;
color: $white;
width: 200px;
}

View File

@ -11,6 +11,11 @@
#blob-content-holder.blob-content-holder
- if @code_navigation_path
#js-code-navigation{ data: { code_navigation_path: @code_navigation_path, blob_path: blob.path, definition_path_prefix: project_blob_path(@project, @ref) } }
%article.file-holder
= render 'projects/blob/header', blob: blob
= render 'projects/blob/content', blob: blob
- if Feature.enabled?(:refactor_blob_viewer, @project, default_enabled: :yaml)
#js-view-blob-app{ data: { blob_path: blob.path } }
.gl-spinner-container
= loading_icon(size: 'md')
- else
%article.file-holder
= render 'projects/blob/header', blob: blob
= render 'projects/blob/content', blob: blob

View File

@ -0,0 +1,5 @@
---
title: Fix N+1 in REST projects and service desk
merge_request: 58747
author:
type: performance

View File

@ -0,0 +1,4 @@
title: Update performance bar background color to use Pajamas compliant colour palette
merge_request: 52775
author: Yogi (@yo)
type: changed

View File

@ -0,0 +1,5 @@
---
title: Enable chronological sort order for other items in the performance bar
merge_request: 58572
author:
type: changed

View File

@ -132,6 +132,7 @@ module API
.preload(:project_setting)
.preload(:container_expiration_policy)
.preload(:auto_devops)
.preload(:service_desk_setting)
.preload(project_group_links: { group: :route },
fork_network: :root_project,
fork_network_member: :forked_from_project,

View File

@ -50,11 +50,11 @@ module Gitlab
end
def recording_request
start = Gitlab::Metrics::System.monotonic_time
@start = Gitlab::Metrics::System.monotonic_time
yield
ensure
@duration += Gitlab::Metrics::System.monotonic_time - start
@duration += Gitlab::Metrics::System.monotonic_time - @start
end
def store_timings
@ -64,8 +64,14 @@ module Gitlab
request_hash = @request.is_a?(Google::Protobuf::MessageExts) ? @request.to_h : {}
GitalyClient.add_call_details(feature: "#{@service}##{@rpc}", duration: @duration, request: request_hash, rpc: @rpc,
backtrace: Gitlab::BacktraceCleaner.clean_backtrace(caller))
GitalyClient.add_call_details(
start: @start,
feature: "#{@service}##{@rpc}",
duration: @duration,
request: request_hash,
rpc: @rpc,
backtrace: Gitlab::BacktraceCleaner.clean_backtrace(caller)
)
end
end
end

View File

@ -37,7 +37,7 @@ module Gitlab
def request(event)
payload = event.payload
add_to_detail_store(payload)
add_to_detail_store(event.time, payload)
add_to_request_store(payload)
expose_metrics(payload)
end
@ -48,10 +48,11 @@ module Gitlab
::Gitlab::Metrics::Transaction.current
end
def add_to_detail_store(payload)
def add_to_detail_store(start, payload)
return unless Gitlab::PerformanceBar.enabled_for_request?
self.class.detail_store << {
start: start,
duration: payload[:duration],
scheme: payload[:scheme],
method: payload[:method],

View File

@ -4194,6 +4194,9 @@ msgstr ""
msgid "Ascending"
msgstr ""
msgid "Ask again later"
msgstr ""
msgid "Ask your group maintainer to set up a group runner."
msgstr ""
@ -17024,9 +17027,6 @@ msgstr ""
msgid "Invalid login or password"
msgstr ""
msgid "Invalid period"
msgstr ""
msgid "Invalid pin code"
msgstr ""
@ -22634,9 +22634,15 @@ msgstr ""
msgid "Performance optimization"
msgstr ""
msgid "PerformanceBar|Backend"
msgstr ""
msgid "PerformanceBar|Bullet notifications"
msgstr ""
msgid "PerformanceBar|DOM Content Loaded"
msgstr ""
msgid "PerformanceBar|Download"
msgstr ""
@ -22646,6 +22652,9 @@ msgstr ""
msgid "PerformanceBar|External Http calls"
msgstr ""
msgid "PerformanceBar|First Contentful Paint"
msgstr ""
msgid "PerformanceBar|Frontend resources"
msgstr ""
@ -27473,6 +27482,9 @@ msgstr ""
msgid "SecurityReports|Although it's rare to have no vulnerabilities, it can happen. Check your settings to make sure you've set up your dashboard correctly."
msgstr ""
msgid "SecurityReports|At GitLab, we're all about iteration and feedback. That's why we are reaching out to customers like you to help guide what we work on this year for Vulnerability Management. We have a lot of exciting ideas and ask that you assist us by taking a short survey %{boldStart}no longer than 10 minutes%{boldEnd} to evaluate a few of our potential features."
msgstr ""
msgid "SecurityReports|Change status"
msgstr ""
@ -27632,6 +27644,9 @@ msgstr ""
msgid "SecurityReports|Status"
msgstr ""
msgid "SecurityReports|Take survey"
msgstr ""
msgid "SecurityReports|There was an error adding the comment."
msgstr ""
@ -27677,6 +27692,9 @@ msgstr ""
msgid "SecurityReports|Upgrade to manage vulnerabilities"
msgstr ""
msgid "SecurityReports|Vulnerability Management feature survey"
msgstr ""
msgid "SecurityReports|Vulnerability Report"
msgstr ""
@ -27692,6 +27710,9 @@ msgstr ""
msgid "SecurityReports|You must sign in as an authorized user to see this report"
msgstr ""
msgid "SecurityReports|Your feedback is important to us! We will ask again in a week."
msgstr ""
msgid "See GitLab's %{password_policy_guidelines}"
msgstr ""

View File

@ -46,7 +46,7 @@ module QA
project&.remove_via_api!
end
it 'merges when pipeline succeeds', :smoke, testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1684' do
it 'merges when pipeline succeeds', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1684' do
branch_name = "merge-request-test-#{SecureRandom.hex(8)}"
# Create a branch that will be merged into the default branch

View File

@ -114,7 +114,7 @@ module QA
end
context 'Geo', :orchestrated, :geo do
it 'replicates a published pypi package to the Geo secondary site', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1120' do
it 'replicates a published pypi package to the Geo secondary site', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1120', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/325556', type: :investigating } do
QA::Runtime::Logger.debug('Visiting the secondary Geo site')
QA::Flow::Login.while_signed_in(address: :geo_secondary) do

View File

@ -290,7 +290,6 @@ RSpec.describe 'Project' do
let(:project) { create(:forked_project_with_submodules) }
before do
stub_feature_flags(refactor_blob_viewer: false)
project.add_maintainer(user)
sign_in user
visit project_path(project)

View File

@ -966,62 +966,6 @@ describe('format24HourTimeStringFromInt', () => {
});
});
describe('getOverlapDateInPeriods', () => {
const start = new Date(2021, 0, 11);
const end = new Date(2021, 0, 13);
describe('when date periods overlap', () => {
const givenPeriodLeft = new Date(2021, 0, 11);
const givenPeriodRight = new Date(2021, 0, 14);
it('returns an overlap object that contains the amount of days overlapping, the amount of hours overlapping, start date of overlap and end date of overlap', () => {
expect(
datetimeUtility.getOverlapDateInPeriods(
{ start, end },
{ start: givenPeriodLeft, end: givenPeriodRight },
),
).toEqual({
daysOverlap: 2,
hoursOverlap: 48,
overlapStartDate: givenPeriodLeft.getTime(),
overlapEndDate: end.getTime(),
});
});
});
describe('when date periods do not overlap', () => {
const givenPeriodLeft = new Date(2021, 0, 9);
const givenPeriodRight = new Date(2021, 0, 10);
it('returns an overlap object that contains a 0 value for days overlapping', () => {
expect(
datetimeUtility.getOverlapDateInPeriods(
{ start, end },
{ start: givenPeriodLeft, end: givenPeriodRight },
),
).toEqual({ daysOverlap: 0 });
});
});
describe('when date periods contain an invalid Date', () => {
const startInvalid = new Date(NaN);
const endInvalid = new Date(NaN);
const error = __('Invalid period');
it('throws an exception when the left period contains an invalid date', () => {
expect(() =>
datetimeUtility.getOverlapDateInPeriods({ start, end }, { start: startInvalid, end }),
).toThrow(error);
});
it('throws an exception when the right period contains an invalid date', () => {
expect(() =>
datetimeUtility.getOverlapDateInPeriods({ start, end }, { start, end: endInvalid }),
).toThrow(error);
});
});
});
describe('isToday', () => {
const today = new Date();
it.each`

View File

@ -120,6 +120,73 @@ describe('detailedMetric', () => {
});
});
describe('when the details have summaryOptions option', () => {
const gitalyDetails = {
duration: '123ms',
calls: 456,
details: requestDetails,
warnings: ['gitaly calls: 456 over 30'],
};
describe('when the details have summaryOptions > hideTotal option', () => {
beforeEach(() => {
createComponent({
currentRequest: {
details: {
gitaly: { ...gitalyDetails, summaryOptions: { hideTotal: true } },
},
},
});
});
it('displays a summary section', () => {
expect(findAllSummaryItems()).toEqual(['Total duration 123ms']);
});
});
describe('when the details have summaryOptions > hideDuration option', () => {
beforeEach(() => {
createComponent({
currentRequest: {
details: {
gitaly: { ...gitalyDetails, summaryOptions: { hideDuration: true } },
},
},
});
});
it('displays a summary section', () => {
expect(findAllSummaryItems()).toEqual(['Total 456']);
});
});
describe('when the details have both summary and summaryOptions field', () => {
beforeEach(() => {
createComponent({
currentRequest: {
details: {
gitaly: {
...gitalyDetails,
summary: {
'In controllers': 100,
'In middlewares': 20,
},
summaryOptions: {
hideDuration: true,
hideTotal: true,
},
},
},
},
});
});
it('displays a summary section', () => {
expect(findAllSummaryItems()).toEqual(['In controllers 100', 'In middlewares 20']);
});
});
});
describe("when the details don't have a start field", () => {
beforeEach(() => {
createComponent({

View File

@ -24,11 +24,14 @@ RSpec.describe Gitlab::GitalyClient::Call do
def expect_call_details_to_match(duration_higher_than: 0)
expect(client.list_call_details.size).to eq(1)
expect(client.list_call_details.first)
.to match a_hash_including(feature: "#{service}##{rpc}",
duration: a_value > duration_higher_than,
request: an_instance_of(Hash),
rpc: rpc,
backtrace: an_instance_of(Array))
.to match a_hash_including(
start: a_value > 0,
feature: "#{service}##{rpc}",
duration: a_value > duration_higher_than,
request: an_instance_of(Hash),
rpc: rpc,
backtrace: an_instance_of(Array)
)
end
context 'when the response is not an enumerator' do

View File

@ -6,29 +6,45 @@ RSpec.describe Gitlab::Metrics::Subscribers::ExternalHttp, :request_store do
let(:transaction) { Gitlab::Metrics::Transaction.new }
let(:subscriber) { described_class.new }
around do |example|
freeze_time { example.run }
end
let(:event_1) do
double(:event, payload: {
method: 'POST', code: "200", duration: 0.321,
scheme: 'https', host: 'gitlab.com', port: 80, path: '/api/v4/projects',
query: 'current=true'
})
double(
:event,
payload: {
method: 'POST', code: "200", duration: 0.321,
scheme: 'https', host: 'gitlab.com', port: 80, path: '/api/v4/projects',
query: 'current=true'
},
time: Time.current
)
end
let(:event_2) do
double(:event, payload: {
method: 'GET', code: "301", duration: 0.12,
scheme: 'http', host: 'gitlab.com', port: 80, path: '/api/v4/projects/2',
query: 'current=true'
})
double(
:event,
payload: {
method: 'GET', code: "301", duration: 0.12,
scheme: 'http', host: 'gitlab.com', port: 80, path: '/api/v4/projects/2',
query: 'current=true'
},
time: Time.current
)
end
let(:event_3) do
double(:event, payload: {
method: 'POST', duration: 5.3,
scheme: 'http', host: 'gitlab.com', port: 80, path: '/api/v4/projects/2/issues',
query: 'current=true',
exception_object: Net::ReadTimeout.new
})
double(
:event,
payload: {
method: 'POST', duration: 5.3,
scheme: 'http', host: 'gitlab.com', port: 80, path: '/api/v4/projects/2/issues',
query: 'current=true',
exception_object: Net::ReadTimeout.new
},
time: Time.current
)
end
describe '.detail_store' do
@ -134,19 +150,22 @@ RSpec.describe Gitlab::Metrics::Subscribers::ExternalHttp, :request_store do
subscriber.request(event_3)
expect(Gitlab::SafeRequestStore[:external_http_detail_store].length).to eq(3)
expect(Gitlab::SafeRequestStore[:external_http_detail_store][0]).to include(
expect(Gitlab::SafeRequestStore[:external_http_detail_store][0]).to match a_hash_including(
start: be_like_time(Time.current),
method: 'POST', code: "200", duration: 0.321,
scheme: 'https', host: 'gitlab.com', port: 80, path: '/api/v4/projects',
query: 'current=true', exception_object: nil,
backtrace: be_a(Array)
)
expect(Gitlab::SafeRequestStore[:external_http_detail_store][1]).to include(
expect(Gitlab::SafeRequestStore[:external_http_detail_store][1]).to match a_hash_including(
start: be_like_time(Time.current),
method: 'GET', code: "301", duration: 0.12,
scheme: 'http', host: 'gitlab.com', port: 80, path: '/api/v4/projects/2',
query: 'current=true', exception_object: nil,
backtrace: be_a(Array)
)
expect(Gitlab::SafeRequestStore[:external_http_detail_store][2]).to include(
expect(Gitlab::SafeRequestStore[:external_http_detail_store][2]).to match a_hash_including(
start: be_like_time(Time.current),
method: 'POST', duration: 5.3,
scheme: 'http', host: 'gitlab.com', port: 80, path: '/api/v4/projects/2/issues',
query: 'current=true',

View File

@ -11,6 +11,10 @@ RSpec.describe Peek::Views::ExternalHttp, :request_store do
allow(Gitlab::PerformanceBar).to receive(:enabled_for_request?).and_return(true)
end
around do |example|
freeze_time { example.run }
end
let(:event_1) do
{
method: 'POST', code: "200", duration: 0.03,
@ -44,9 +48,9 @@ RSpec.describe Peek::Views::ExternalHttp, :request_store do
end
it 'returns aggregated results' do
subscriber.request(double(:event, payload: event_1))
subscriber.request(double(:event, payload: event_2))
subscriber.request(double(:event, payload: event_3))
subscriber.request(double(:event, payload: event_1, time: Time.current))
subscriber.request(double(:event, payload: event_2, time: Time.current))
subscriber.request(double(:event, payload: event_3, time: Time.current))
results = subject.results
expect(results[:calls]).to eq(3)
@ -55,6 +59,7 @@ RSpec.describe Peek::Views::ExternalHttp, :request_store do
expected = [
{
start: be_like_time(Time.current),
duration: 30.0,
label: "POST https://gitlab.com:80/api/v4/projects?current=true",
code: "Response status: 200",
@ -63,6 +68,7 @@ RSpec.describe Peek::Views::ExternalHttp, :request_store do
warnings: []
},
{
start: be_like_time(Time.current),
duration: 1300,
label: "POST http://gitlab.com:80/api/v4/projects/2/issues?current=true",
code: nil,
@ -71,6 +77,7 @@ RSpec.describe Peek::Views::ExternalHttp, :request_store do
warnings: ["1300.0 over 100"]
},
{
start: be_like_time(Time.current),
duration: 5.0,
label: "GET http://gitlab.com:80/api/v4/projects/2?current=true",
code: "Response status: 301",
@ -81,7 +88,7 @@ RSpec.describe Peek::Views::ExternalHttp, :request_store do
]
expect(
results[:details].map { |data| data.slice(:duration, :label, :code, :proxy, :error, :warnings) }
results[:details].map { |data| data.slice(:start, :duration, :label, :code, :proxy, :error, :warnings) }
).to match_array(expected)
end
@ -91,10 +98,11 @@ RSpec.describe Peek::Views::ExternalHttp, :request_store do
end
it 'displays IPv4 in the label' do
subscriber.request(double(:event, payload: event_1))
subscriber.request(double(:event, payload: event_1, time: Time.current))
expect(subject.results[:details]).to contain_exactly(
a_hash_including(
start: be_like_time(Time.current),
duration: 30.0,
label: "POST https://1.2.3.4:80/api/v4/projects?current=true",
code: "Response status: 200",
@ -112,10 +120,11 @@ RSpec.describe Peek::Views::ExternalHttp, :request_store do
end
it 'displays IPv6 in the label' do
subscriber.request(double(:event, payload: event_1))
subscriber.request(double(:event, payload: event_1, time: Time.current))
expect(subject.results[:details]).to contain_exactly(
a_hash_including(
start: be_like_time(Time.current),
duration: 30.0,
label: "POST https://[2606:4700:90:0:f22e:fbec:5bed:a9b9]:80/api/v4/projects?current=true",
code: "Response status: 200",
@ -133,10 +142,11 @@ RSpec.describe Peek::Views::ExternalHttp, :request_store do
end
it 'converts query hash into a query string' do
subscriber.request(double(:event, payload: event_1))
subscriber.request(double(:event, payload: event_1, time: Time.current))
expect(subject.results[:details]).to contain_exactly(
a_hash_including(
start: be_like_time(Time.current),
duration: 30.0,
label: "POST https://gitlab.com:80/api/v4/projects?current=true&item1=string&item2%5B%5D=1&item2%5B%5D=2",
code: "Response status: 200",
@ -154,10 +164,11 @@ RSpec.describe Peek::Views::ExternalHttp, :request_store do
end
it 'displays unknown in the label' do
subscriber.request(double(:event, payload: event_1))
subscriber.request(double(:event, payload: event_1, time: Time.current))
expect(subject.results[:details]).to contain_exactly(
a_hash_including(
start: be_like_time(Time.current),
duration: 30.0,
label: "POST unknown",
code: "Response status: 200",
@ -176,10 +187,11 @@ RSpec.describe Peek::Views::ExternalHttp, :request_store do
end
it 'displays unknown in the label' do
subscriber.request(double(:event, payload: event_1))
subscriber.request(double(:event, payload: event_1, time: Time.current))
expect(subject.results[:details]).to contain_exactly(
a_hash_including(
start: be_like_time(Time.current),
duration: 30.0,
label: "POST unknown",
code: "Response status: 200",
@ -198,10 +210,11 @@ RSpec.describe Peek::Views::ExternalHttp, :request_store do
end
it 'displays unknown in the label' do
subscriber.request(double(:event, payload: event_1))
subscriber.request(double(:event, payload: event_1, time: Time.current))
expect(subject.results[:details]).to contain_exactly(
a_hash_including(
start: be_like_time(Time.current),
duration: 30.0,
label: "POST unknown",
code: "Response status: 200",

View File

@ -835,6 +835,29 @@ RSpec.describe API::Projects do
end.not_to exceed_query_limit(control.count)
end
end
context 'when service desk is enabled', :use_clean_rails_memory_store_caching do
let_it_be(:admin) { create(:admin) }
it 'avoids N+1 queries' do
allow(Gitlab::ServiceDeskEmail).to receive(:enabled?).and_return(true)
allow(Gitlab::IncomingEmail).to receive(:enabled?).and_return(true)
get api('/projects', admin)
create(:project, :public, :service_desk_enabled, namespace: admin.namespace)
control = ActiveRecord::QueryRecorder.new do
get api('/projects', admin)
end
create_list(:project, 2, :public, :service_desk_enabled, namespace: admin.namespace)
expect do
get api('/projects', admin)
end.not_to exceed_query_limit(control.count)
end
end
end
describe 'POST /projects' do

View File

@ -277,6 +277,11 @@ RSpec.configure do |config|
# Vue issues page has feature parity with the current Haml page
stub_feature_flags(vue_issues_list: false)
# Disable `refactor_blob_viewer` as we refactor
# the blob viewer. See the follwing epic for more:
# https://gitlab.com/groups/gitlab-org/-/epics/5531
stub_feature_flags(refactor_blob_viewer: false)
allow(Gitlab::GitalyClient).to receive(:can_use_disk?).and_return(enable_rugged)
else
unstub_all_feature_flags