Commit 5a1541a4 authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@13-9-stable-ee

parent f1bc6c9f
......@@ -2,7 +2,6 @@
import { GlAlert, GlFormCheckbox, GlLink } from '@gitlab/ui';
import { __ } from '~/locale';
import UpdateKeepLatestArtifactProjectSetting from './graphql/mutations/update_keep_latest_artifact_project_setting.mutation.graphql';
import GetKeepLatestArtifactApplicationSetting from './graphql/queries/get_keep_latest_artifact_application_setting.query.graphql';
import GetKeepLatestArtifactProjectSetting from './graphql/queries/get_keep_latest_artifact_project_setting.query.graphql';
export default {
......@@ -14,7 +13,6 @@ export default {
enabledHelpText: __(
'The latest artifacts created by jobs in the most recent successful pipeline will be stored.',
),
disabledHelpText: __('This feature is disabled at the instance level.'),
helpLinkText: __('More information'),
checkboxText: __('Keep artifacts from most recent successful jobs'),
},
......@@ -46,19 +44,12 @@ export default {
this.reportError(this.$options.errors.fetchError);
},
},
projectSettingDisabled: {
query: GetKeepLatestArtifactApplicationSetting,
update(data) {
return !data.ciApplicationSettings?.keepLatestArtifact;
},
},
},
data() {
return {
keepLatestArtifact: null,
errorMessage: '',
isAlertDismissed: false,
projectSettingDisabled: true,
};
},
computed: {
......@@ -66,9 +57,7 @@ export default {
return this.errorMessage && !this.isAlertDismissed;
},
helpText() {
return this.projectSettingDisabled
? this.$options.i18n.disabledHelpText
: this.$options.i18n.enabledHelpText;
return this.$options.i18n.enabledHelpText;
},
},
methods: {
......@@ -106,10 +95,7 @@ export default {
@dismiss="isAlertDismissed = true"
>{{ errorMessage }}</gl-alert
>
<gl-form-checkbox
v-model="keepLatestArtifact"
:disabled="projectSettingDisabled"
@change="updateSetting"
<gl-form-checkbox v-model="keepLatestArtifact" @change="updateSetting"
><strong class="gl-mr-3">{{ $options.i18n.checkboxText }}</strong>
<gl-link :href="helpPagePath">{{ $options.i18n.helpLinkText }}</gl-link>
<template v-if="!$apollo.loading" #help>{{ helpText }}</template>
......
......@@ -15,7 +15,6 @@ export default function initClonePanel() {
}
$('a', $cloneOptions).on('click', (e) => {
e.preventDefault();
const $this = $(e.currentTarget);
const url = $this.attr('href');
if (url && (url.startsWith('vscode://') || url.startsWith('xcode://'))) {
......
......@@ -368,7 +368,7 @@ export default {
<alert-details-table :alert="alert" :loading="loading" />
</gl-tab>
<gl-tab
v-if="isThreatMonitoringPage"
v-if="!isThreatMonitoringPage"
:data-testid="$options.tabsConfig[1].id"
:title="$options.tabsConfig[1].title"
>
......
......@@ -280,7 +280,7 @@ module ApplicationHelper
def page_class
class_names = []
class_names << 'issue-boards-page gl-overflow-hidden' if current_controller?(:boards)
class_names << 'issue-boards-page gl-overflow-auto' if current_controller?(:boards)
class_names << 'environment-logs-page' if current_controller?(:logs)
class_names << 'with-performance-bar' if performance_bar_enabled?
class_names << system_message_class
......
......@@ -43,7 +43,7 @@ class Repository
changelog license_blob license_key gitignore
gitlab_ci_yml branch_names tag_names branch_count
tag_count avatar exists? root_ref merged_branch_names
has_visible_content? issue_template_names_by_category merge_request_template_names_by_category
has_visible_content? issue_template_names_hash merge_request_template_names_hash
user_defined_metrics_dashboard_paths xcode_project? has_ambiguous_refs?).freeze
# Methods that use cache_method but only memoize the value
......@@ -60,8 +60,8 @@ class Repository
gitignore: :gitignore,
gitlab_ci: :gitlab_ci_yml,
avatar: :avatar,
issue_template: :issue_template_names_by_category,
merge_request_template: :merge_request_template_names_by_category,
issue_template: :issue_template_names_hash,
merge_request_template: :merge_request_template_names_hash,
metrics_dashboard: :user_defined_metrics_dashboard_paths,
xcode_config: :xcode_project?
}.freeze
......@@ -573,15 +573,15 @@ class Repository
cache_method :avatar
# store issue_template_names as hash
def issue_template_names_by_category
def issue_template_names_hash
Gitlab::Template::IssueTemplate.repository_template_names(project)
end
cache_method :issue_template_names_by_category, fallback: {}
cache_method :issue_template_names_hash, fallback: {}
def merge_request_template_names_by_category
def merge_request_template_names_hash
Gitlab::Template::MergeRequestTemplate.repository_template_names(project)
end
cache_method :merge_request_template_names_by_category, fallback: {}
cache_method :merge_request_template_names_hash, fallback: {}
def user_defined_metrics_dashboard_paths
Gitlab::Metrics::Dashboard::RepoDashboardFinder.list_dashboards(project)
......
......@@ -5,6 +5,12 @@ module Issues
include Gitlab::Routing.url_helpers
include GitlabRoutingHelper
def initialize(issuables_relation, project)
super
@labels = @issuables.labels_hash.transform_values { |labels| labels.sort.join(',').presence }
end
def email(user)
Notify.issues_csv_email(user, project, csv_data, csv_builder.status).deliver_now
end
......@@ -12,7 +18,7 @@ module Issues
private
def associations_to_preload
%i(author assignees timelogs milestone)
%i(author assignees timelogs milestone project)
end
def header_to_value_hash
......@@ -41,7 +47,7 @@ module Issues
end
def issue_labels(issue)
issuables.labels_hash[issue.id].sort.join(',').presence
@labels[issue.id]
end
# rubocop: disable CodeReuse/ActiveRecord
......
......@@ -45,16 +45,17 @@
.settings-content
= render 'projects/runners/index'
%section.settings.no-animate#js-artifacts-settings{ class: ('expanded' if expanded) }
.settings-header
%h4
= _("Artifacts")
%button.btn.gl-button.btn-default.js-settings-toggle{ type: 'button' }
= expanded ? _('Collapse') : _('Expand')
%p
= _("A job artifact is an archive of files and directories saved by a job when it finishes.")
.settings-content
#js-artifacts-settings-app{ data: { full_path: @project.full_path, help_page_path: help_page_path('ci/pipelines/job_artifacts', anchor: 'keep-artifacts-from-most-recent-successful-jobs') } }
- if Gitlab::CurrentSettings.current_application_settings.keep_latest_artifact?
%section.settings.no-animate#js-artifacts-settings{ class: ('expanded' if expanded) }
.settings-header
%h4
= _("Artifacts")
%button.btn.gl-button.btn-default.js-settings-toggle{ type: 'button' }
= expanded ? _('Collapse') : _('Expand')
%p
= _("A job artifact is an archive of files and directories saved by a job when it finishes.")
.settings-content
#js-artifacts-settings-app{ data: { full_path: @project.full_path, help_page_path: help_page_path('ci/pipelines/job_artifacts', anchor: 'keep-artifacts-from-most-recent-successful-jobs') } }
%section.qa-variables-settings.settings.no-animate#js-cicd-variables-settings{ class: ('expanded' if expanded), data: { qa_selector: 'variables_settings_content' } }
.settings-header
......
......@@ -36,7 +36,7 @@ start_foreground()
stop()
{
get_puma_pid
kill -QUIT "$(get_puma_pid)"
kill -INT "$(get_puma_pid)"
}
reload()
......
......@@ -36,7 +36,7 @@ start_foreground()
stop()
{
get_puma_pid
kill -QUIT "$(get_puma_pid)"
kill -INT "$(get_puma_pid)"
}
reload()
......
---
title: Fix Metric tab not showing up on operations page
merge_request: 54736
author:
type: fixed
---
title: Fix creating the idx_on_issues_where_service_desk_reply_to_is_not_null index
before the post migration
merge_request: 54346
author:
type: other
---
title: Fix keep latest artifacts checkbox being always disabled
merge_request: 54669
author:
type: fixed
---
title: Updates authorization for linting endpoint
merge_request: 54492
author:
type: changed
---
title: Restore missing horizontal scrollbar on issue boards
merge_request: 54634
author:
type: fixed
---
title: Send SIGINT instead of SIGQUIT to puma
merge_request: 54446
author: Jörg Behrmann @behrmann
type: fixed
---
title: Reset description template names cache key to reload an updated templates structure
merge_request: 54614
author:
type: fixed
---
title: Fix S3 object storage failing when endpoint is not specified
merge_request: 54868
author:
type: fixed
---
title: Fix N+1 SQL regression in exporting issues to CSV
merge_request: 54287
author:
type: performance
This diff is collapsed.
# frozen_string_literal: true
class AddServiceDeskReplyToIsNotNullIndexOnIssuesFix < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
INDEX_NAME = 'idx_on_issues_where_service_desk_reply_to_is_not_null'
disable_ddl_transaction!
def up
add_concurrent_index(:issues, [:id], name: INDEX_NAME, where: 'service_desk_reply_to IS NOT NULL')
end
def down
remove_concurrent_index_by_name(:issues, INDEX_NAME)
end
end
# frozen_string_literal: true
class AddServiceDeskReplyToIsNotNullIndexOnIssues < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
INDEX_NAME = 'idx_on_issues_where_service_desk_reply_to_is_not_null'
disable_ddl_transaction!
def up
add_concurrent_index(:issues, [:id], name: INDEX_NAME, where: 'service_desk_reply_to IS NOT NULL')
end
def down
remove_concurrent_index_by_name(:issues, INDEX_NAME)
def change
# no-op, the migration's version number was lowered to be executed earlier than db/post_migrate/20201128210234_schedule_populate_issue_email_participants.rb
#
# The new migration is located here: db/migrate/20201128210000_add_service_desk_reply_to_is_not_null_index_on_issues_fix.rb
end
end
2f7415e3e3e66f326f2f65c38406c2103d5075493c86a836497c3541655f4e86
\ No newline at end of file
......@@ -11,7 +11,7 @@ module API
optional :include_merged_yaml, type: Boolean, desc: 'Whether or not to include merged CI config yaml in the response'
end
post '/lint' do
unauthorized! unless Gitlab::CurrentSettings.signup_enabled? && current_user
unauthorized! if Gitlab::CurrentSettings.signup_disabled? && current_user.nil?
result = Gitlab::Ci::YamlProcessor.new(params[:content], user: current_user).execute
......
......@@ -3,6 +3,10 @@
module Gitlab
module CurrentSettings
class << self
def signup_disabled?
!signup_enabled?
end
def current_application_settings
Gitlab::SafeRequestStore.fetch(:current_application_settings) { ensure_application_settings! }
end
......
......@@ -42,7 +42,7 @@ module Gitlab
def ensure_repository_does_not_exist!
if repository.exists?
shared.logger.info(
message: %Q{Deleting existing "#{repository.path}" to re-import it.}
message: %Q{Deleting existing "#{repository.disk_path}" to re-import it.}
)
Repositories::DestroyService.new(repository).execute
......
......@@ -108,7 +108,7 @@ module Gitlab
# Gitaly the actual template names within a given project's repository for all file templates
# other than `issue` and `merge request` description templates, which would instead
# overwrite the `template_names` method to return a redis cached version, by reading cached values
# from `repository.issue_template_names_by_category` and `repository.merge_request_template_names_by_category`
# from `repository.issue_template_names_hash` and `repository.merge_request_template_names_hash`
# methods.
def repository_template_names(project)
template_names_by_category(self.all(project))
......
......@@ -23,7 +23,7 @@ module Gitlab
# own caching mechanism to avoid the back and forth call jumps between finder and model.
#
# follow-up issue: https://gitlab.com/gitlab-org/gitlab/-/issues/300279
project.repository.issue_template_names_by_category
project.repository.issue_template_names_hash
end
end
end
......
......@@ -23,7 +23,7 @@ module Gitlab
# own caching mechanism to avoid the back and forth call jumps between finder and model.
#
# follow-up issue: https://gitlab.com/gitlab-org/gitlab/-/issues/300279
project.repository.merge_request_template_names_by_category
project.repository.merge_request_template_names_hash
end
end
end
......
......@@ -2,8 +2,6 @@
module ObjectStorage
class Config
include Gitlab::Utils::StrongMemoize
AWS_PROVIDER = 'AWS'
AZURE_PROVIDER = 'AzureRM'
GOOGLE_PROVIDER = 'Google'
......@@ -68,36 +66,6 @@ module ObjectStorage
def provider
credentials[:provider].to_s
end
# This method converts fog-aws parameters to an endpoint for the
# Workhorse S3 client.
def s3_endpoint
strong_memoize(:s3_endpoint) do
# We could omit this line and let the following code handle this, but
# this will ensure that working configurations that use `endpoint`
# will continue to work.
next credentials[:endpoint] if credentials[:endpoint].present?
generate_s3_endpoint_from_credentials
end
end
def generate_s3_endpoint_from_credentials
# fog-aws has special handling of the host, region, scheme, etc:
# https://github.com/fog/fog-aws/blob/c7a11ba377a76d147861d0e921eb1e245bc11b6c/lib/fog/aws/storage.rb#L440-L449
# Rather than reimplement this, we derive it from a sample GET URL.
url = fog_connection.get_object_url(bucket, "tmp", nil)
uri = ::Addressable::URI.parse(url)
return unless uri&.scheme && uri&.host
endpoint = "#{uri.scheme}://#{uri.host}"
endpoint += ":#{uri.port}" if uri.port
endpoint
rescue ::URI::InvalidComponentError, ::Addressable::URI::InvalidURIError => e
Gitlab::ErrorTracking.track_exception(e)
nil
end
# End AWS-specific options
# Begin Azure-specific options
......@@ -123,10 +91,6 @@ module ObjectStorage
end
end
def fog_connection
@connection ||= ::Fog::Storage.new(credentials)
end
private
# This returns a Hash of HTTP encryption headers to send along to S3.
......
......@@ -80,7 +80,7 @@ module ObjectStorage
S3Config: {
Bucket: bucket_name,
Region: credentials[:region],
Endpoint: config.s3_endpoint,
Endpoint: credentials[:endpoint],
PathStyle: config.use_path_style?,
UseIamProfile: config.use_iam_profile?,
ServerSideEncryption: config.server_side_encryption,
......@@ -229,7 +229,7 @@ module ObjectStorage
end
def connection
config.fog_connection
@connection ||= ::Fog::Storage.new(credentials)
end
end
end
......@@ -30012,9 +30012,6 @@ msgstr ""
msgid "This epic does not exist or you don't have sufficient permission."
msgstr ""
msgid "This feature is disabled at the instance level."
msgstr ""
msgid "This feature requires local storage to be enabled"
msgstr ""
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Keep latest artifact checkbox when application keep latest artifact setting is disabled checkbox is disabled when application setting is disabled 1`] = `
<div>
<!---->
<b-form-checkbox-stub
checked="true"
class="gl-form-checkbox"
disabled="true"
plain="true"
value="true"
>
<strong
class="gl-mr-3"
>
Keep artifacts from most recent successful jobs
</strong>
<gl-link-stub
href="/help/ci/pipelines/job_artifacts"
>
More information
</gl-link-stub>
<p
class="help-text"
>
This feature is disabled at the instance level.
</p>
</b-form-checkbox-stub>
</div>
`;
exports[`Keep latest artifact checkbox when application keep latest artifact setting is enabled sets correct setting value in checkbox with query result 1`] = `
<div>
<!---->
......
......@@ -120,23 +120,4 @@ describe('Keep latest artifact checkbox', () => {
expect(findCheckbox().attributes('disabled')).toBeUndefined();
});
});
describe('when application keep latest artifact setting is disabled', () => {
it('checkbox is disabled when application setting is disabled', async () => {
createComponent({
keepLatestArtifactApplicationQueryHandler: jest.fn().mockResolvedValue({
data: {
ciApplicationSettings: {
keepLatestArtifact: false,
},
},
}),
});
await wrapper.vm.$nextTick();
expect(wrapper.element).toMatchSnapshot();
expect(findCheckbox().attributes('disabled')).toBe('true');
});
});
});
......@@ -128,6 +128,10 @@ describe('AlertDetails', () => {
expect(wrapper.findByTestId('startTimeItem').exists()).toBe(true);
expect(wrapper.findByTestId('startTimeItem').props('time')).toBe(mockAlert.startedAt);
});
it('renders the metrics tab', () => {
expect(findMetricsTab().exists()).toBe(true);
});
});
describe('individual alert fields', () => {
......@@ -179,7 +183,8 @@ describe('AlertDetails', () => {
describe('Threat Monitoring details', () => {
it('should not render the metrics tab', () => {
mountComponent({
data: { alert: mockAlert, provide: { isThreatMonitoringPage: true } },
data: { alert: mockAlert },
provide: { isThreatMonitoringPage: true },
});
expect(findMetricsTab().exists()).toBe(false);
});
......
......@@ -24,6 +24,26 @@ RSpec.describe Gitlab::CurrentSettings do
end
end
describe '.signup_disabled?' do
subject { described_class.signup_disabled? }
context 'when signup is enabled' do
before do
create(:application_setting, signup_enabled: true)
end
it { is_expected.to be_falsey }
end
context 'when signup is disabled' do
before do
create(:application_setting, signup_enabled: false)
end
it { is_expected.to be_truthy }
end
end
describe '#current_application_settings', :use_clean_rails_memory_store_caching do
it 'allows keys to be called directly' do
db_settings = create(:application_setting,
......
......@@ -46,7 +46,7 @@ RSpec.describe Gitlab::ImportExport::RepoRestorer do
context 'when the repository already exists' do
it 'deletes the existing repository before importing' do
allow(project.repository).to receive(:exists?).and_return(true)
allow(project.repository).to receive(:path).and_return('repository_path')
allow(project.repository).to receive(:disk_path).and_return('repository_path')
expect_next_instance_of(Repositories::DestroyService) do |instance|
expect(instance).to receive(:execute).and_call_original
......
# frozen_string_literal: true
require 'spec_helper'
require 'fast_spec_helper'
require 'rspec-parameterized'
require 'fog/core'
RSpec.describe ObjectStorage::Config do
using RSpec::Parameterized::TableSyntax
......@@ -33,9 +34,7 @@ RSpec.describe ObjectStorage::Config do
}
end
subject do
described_class.new(raw_config.as_json)
end
subject { described_class.new(raw_config.as_json) }
describe '#load_provider' do
before do
......@@ -46,10 +45,6 @@ RSpec.describe ObjectStorage::Config do
it 'registers AWS as a provider' do
expect(Fog.providers.keys).to include(:aws)
end
describe '#fog_connection' do
it { expect(subject.fog_connection).to be_a_kind_of(Fog::AWS::Storage::Real) }
end
end
context 'with Google' do
......@@ -64,10 +59,6 @@ RSpec.describe ObjectStorage::Config do
it 'registers Google as a provider' do
expect(Fog.providers.keys).to include(:google)
end
describe '#fog_connection' do
it { expect(subject.fog_connection).to be_a_kind_of(Fog::Storage::GoogleXML::Real) }
end
end
context 'with Azure' do
......@@ -82,10 +73,6 @@ RSpec.describe ObjectStorage::Config do
it 'registers AzureRM as a provider' do
expect(Fog.providers.keys).to include(:azurerm)
end
describe '#fog_connection' do
it { expect(subject.fog_connection).to be_a_kind_of(Fog::Storage::AzureRM::Real) }
end
end
end
......@@ -183,50 +170,6 @@ RSpec.describe ObjectStorage::Config do
it { expect(subject.provider).to eq('AWS') }
it { expect(subject.aws?).to be true }
it { expect(subject.google?).to be false }
it 'returns the default S3 endpoint' do
subject.load_provider
expect(subject.s3_endpoint).to eq("https://test-bucket.s3.amazonaws.com")
end
describe 'with a custom endpoint' do
let(:endpoint) { 'https://my.example.com' }
before do
credentials[:endpoint] = endpoint
end
it 'returns the custom endpoint' do
subject.load_provider
expect(subject.s3_endpoint).to eq(endpoint)
end
end
context 'with custom S3 host and port' do
where(:host, :port, :scheme, :expected) do
's3.example.com' | 8080 | nil | 'https://test-bucket.s3.example.com:8080'
's3.example.com' | 443 | nil | 'https://test-bucket.s3.example.com'
's3.example.com' | 443 | "https" | 'https://test-bucket.s3.example.com'
's3.example.com' | nil | nil | 'https://test-bucket.s3.example.com'
's3.example.com' | 80 | "http" | 'http://test-bucket.s3.example.com'
's3.example.com' | "bogus" | nil | nil
end
with_them do
before do
credentials[:host] = host
credentials[:port] = port
credentials[:scheme] = scheme
subject.load_provider
end
it 'returns expected host' do
expect(subject.s3_endpoint).to eq(expected)
end
end
end
end
context 'with Google credentials' do
......
......@@ -1949,8 +1949,8 @@ RSpec.describe Repository do
:root_ref,
:merged_branch_names,
:has_visible_content?,
:issue_template_names_by_category,
:merge_request_template_names_by_category,
:issue_template_names_hash,
:merge_request_template_names_hash,
:user_defined_metrics_dashboard_paths,
:xcode_project?,
:has_ambiguous_refs?
......
......@@ -5,7 +5,9 @@ require 'spec_helper'
RSpec.describe API::Lint do
describe 'POST /ci/lint' do
context 'when signup settings are disabled' do
Gitlab::CurrentSettings.signup_enabled = false
before do
Gitlab::CurrentSettings.signup_enabled = false
end
context 'when unauthenticated' do
it 'returns authentication error' do
......@@ -16,22 +18,25 @@ RSpec.describe API::Lint do
end
context 'when authenticated' do
it 'returns unauthorized error' do
post api('/ci/lint'), params: { content: 'content' }
let_it_be(:api_user) { create(:user) }
it 'returns authorized' do
post api('/ci/lint', api_user), params: { content: 'content' }
expect(response).to have_gitlab_http_status(:unauthorized)
expect(response).to have_gitlab_http_status(:ok)
end
end
end
context 'when signup settings are enabled' do
Gitlab::CurrentSettings.signup_enabled = true
before do
Gitlab::CurrentSettings.signup_enabled = true
end
context 'when unauthenticated' do
it 'returns authentication error' do
it 'returns authorized success' do
post api('/ci/lint'), params: { content: 'content' }
expect(response).to have_gitlab_http_status(:unauthorized)
expect(response).to have_gitlab_http_status(:ok)
end
end
......
......@@ -4,11 +4,11 @@ require 'spec_helper'
RSpec.describe Issues::ExportCsvService do
let_it_be(:user) { create(:user) }
let(:group) { create(:group) }
let(:project) { create(:project, :public, group: group) }
let!(:issue) { create(:issue, project: project, author: user) }
let!(:bad_issue) { create(:issue, project: project, author: user) }
let(:subject) { described_class.new(Issue.all, project) }
let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project, :public, group: group) }
let_it_be(:issue) { create(:issue, project: project, author: user) }
let_it_be(:bad_issue) { create(:issue, project: project, author: user) }
subject { described_class.new(Issue.all, project) }
it 'renders csv to string' do
expect(subject.csv_data).to be_a String
......@@ -33,11 +33,11 @@ RSpec.describe Issues::ExportCsvService do
end
context 'includes' do
let(:milestone) { create(:milestone, title: 'v1.0', project: project) }
let(:idea_label) { create(:label, project: project, title: 'Idea') }
let(:feature_label) { create(:label, project: project, title: 'Feature') }
let_it_be(:milestone) { create(:milestone, title: 'v1.0', project: project) }
let_it_be(:idea_label) { create(:label, project: project, title: 'Idea') }
let_it_be(:feature_label) { create(:label, project: project, title: 'Feature') }
before do
before_all do
# Creating a timelog touches the updated_at timestamp of issue,
# so create these first.
issue.timelogs.create!(time_spent: 360, user: user)
......@@ -60,6 +60,10 @@ RSpec.describe Issues::ExportCsvService do
expect(csv.headers).to include('Title', 'Description')
end
it 'returns two issues' do
expect(csv.count).to eq(2)
end
specify 'iid' do
expect(csv[0]['Issue ID']).to eq issue.iid.to_s
end
......@@ -150,7 +154,7 @@ RSpec.describe Issues::ExportCsvService do
end
context 'with issues filtered by labels and project' do
let(:subject) do
subject do
described_class.new(
IssuesFinder.new(user,
project_id: project.id,
......@@ -162,6 +166,27 @@ RSpec.describe Issues::ExportCsvService do
expect(csv[0]['Issue ID']).to eq issue.iid.to_s
end
end
context 'with label links' do
let(:labeled_issues) { create_list(:labeled_issue, 2, project: project, author: user, labels: [feature_label, idea_label]) }
it 'does not run a query for each label link' do
control_count = ActiveRecord::QueryRecorder.new { csv }.count
labeled_issues
expect { csv }.not_to exceed_query_limit(control_count)
expect(csv.count).to eq(4)
end
it 'returns the labels in sorted order' do
labeled_issues
labeled_rows = csv.select { |entry| labeled_issues.map(&:iid).include?(entry['Issue ID'].to_i) }
expect(labeled_rows.count).to eq(2)
expect(labeled_rows.map { |entry| entry['Labels'] }).to all( eq("Feature,Idea") )
end
end
end
context 'with minimal details' do
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment