Commit b2442147 authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab master

parents e37d80e2 762e049d
...@@ -447,6 +447,10 @@ class Issue < ApplicationRecord ...@@ -447,6 +447,10 @@ class Issue < ApplicationRecord
issue_email_participants.pluck(IssueEmailParticipant.arel_table[:email].lower) issue_email_participants.pluck(IssueEmailParticipant.arel_table[:email].lower)
end end
def issue_assignee_user_ids
issue_assignees.pluck(:user_id)
end
private private
# Ensure that the metrics association is safely created and respecting the unique constraint on issue_id # Ensure that the metrics association is safely created and respecting the unique constraint on issue_id
......
...@@ -58,8 +58,10 @@ export default { ...@@ -58,8 +58,10 @@ export default {
targetUrl: { targetUrl: {
field: 'targetUrl', field: 'targetUrl',
label: s__('APIFuzzing|Target URL'), label: s__('APIFuzzing|Target URL'),
description: s__('APIFuzzing|Base URL of API fuzzing target.'), description: s__(
placeholder: __('Ex: Example.com'), 'APIFuzzing|Base URL of API testing target. For example, http://www.example.com.',
),
placeholder: __('http://www.example.com'),
value: '', value: '',
}, },
scanMode: { scanMode: {
...@@ -84,9 +86,9 @@ export default { ...@@ -84,9 +86,9 @@ export default {
field: 'username', field: 'username',
label: s__('APIFuzzing|Username for basic authentication'), label: s__('APIFuzzing|Username for basic authentication'),
description: s__( description: s__(
'APIFuzzing|Instead of entering the username directly, enter the key of the CI variable set to the username.', 'APIFuzzing|Enter the name of the variable containing the username. For example, $VariableWithUsername.',
), ),
placeholder: s__('APIFuzzing|Ex: $TestUsername'), placeholder: s__('APIFuzzing|$VariableWithUsername'),
value: '', value: '',
}, },
{ {
...@@ -94,18 +96,18 @@ export default { ...@@ -94,18 +96,18 @@ export default {
field: 'password', field: 'password',
label: s__('APIFuzzing|Password for basic authentication'), label: s__('APIFuzzing|Password for basic authentication'),
description: s__( description: s__(
'APIFuzzing|Instead of entering the password directly, enter the key of the CI variable set to the password.', 'APIFuzzing|Enter the name of the variable containing the password. For example, $VariableWithPassword.',
), ),
placeholder: s__('APIFuzzing|Ex: $TestPassword'), placeholder: s__('APIFuzzing|$VariableWithPassword'),
value: '', value: '',
}, },
], ],
scanProfile: { scanProfile: {
field: 'scanProfile', field: 'scanProfile',
label: s__('APIFuzzing|Scan profile'), label: s__('APIFuzzing|Scan profile'),
description: 'Pre-defined profiles by GitLab.',
value: '', value: '',
defaultText: s__('APIFuzzing|Choose a profile'), defaultText: s__('APIFuzzing|Choose a profile'),
sectionHeader: s__('APIFuzzing|Predefined profiles'),
options: this.apiFuzzingCiConfiguration.scanProfiles.map( options: this.apiFuzzingCiConfiguration.scanProfiles.map(
({ name: value, description: text }) => ({ ({ name: value, description: text }) => ({
value, value,
...@@ -227,7 +229,7 @@ export default { ...@@ -227,7 +229,7 @@ export default {
<gl-sprintf <gl-sprintf
:message=" :message="
s__( s__(
'APIFuzzing|Authentication is handled by providing HTTP basic authentication token as a header or cookie. %{linkStart}More information%{linkEnd}.', 'APIFuzzing|Configure HTTP basic authentication values. Other authentication methods are supported. %{linkStart}Learn more%{linkEnd}.',
) )
" "
> >
......
...@@ -10,11 +10,11 @@ export const SCAN_MODES = { ...@@ -10,11 +10,11 @@ export const SCAN_MODES = {
), ),
}, },
OPENAPI: { OPENAPI: {
scanModeLabel: __('Open API'), scanModeLabel: __('OpenAPI'),
label: __('Open API specification file path'), label: __('OpenAPI specification file path'),
placeholder: s__('APIFuzzing|Ex: Project_Test/File/example_fuzz.json'), placeholder: s__('APIFuzzing|/folder/example_file.json'),
description: s__( description: s__(
'APIFuzzing|We recommend that you review the JSON specifications file before adding it to a repository.', 'APIFuzzing|File path containing APIs to be tested. For example, /folder/example_file.json.',
), ),
}, },
POSTMAN: { POSTMAN: {
......
<script> <script>
import { GlFormGroup, GlDropdown, GlDropdownItem, GlFormText, GlLink, GlSprintf } from '@gitlab/ui'; import {
GlFormGroup,
GlDropdown,
GlDropdownSectionHeader,
GlDropdownItem,
GlFormText,
GlLink,
GlSprintf,
} from '@gitlab/ui';
import { CUSTOM_VALUE_MESSAGE } from './constants'; import { CUSTOM_VALUE_MESSAGE } from './constants';
export default { export default {
components: { components: {
GlFormGroup, GlFormGroup,
GlDropdown, GlDropdown,
GlDropdownSectionHeader,
GlDropdownItem, GlDropdownItem,
GlFormText, GlFormText,
GlLink, GlLink,
...@@ -43,13 +52,19 @@ export default { ...@@ -43,13 +52,19 @@ export default {
}, },
description: { description: {
type: String, type: String,
required: true, required: false,
default: '',
}, },
disabled: { disabled: {
type: Boolean, type: Boolean,
required: false, required: false,
default: false, default: false,
}, },
sectionHeader: {
type: String,
required: false,
default: '',
},
options: { options: {
type: Array, type: Array,
required: true, required: true,
...@@ -83,10 +98,17 @@ export default { ...@@ -83,10 +98,17 @@ export default {
<gl-form-group :label-for="field"> <gl-form-group :label-for="field">
<template #label> <template #label>
{{ label }} {{ label }}
<gl-form-text class="gl-mt-3">{{ description }}</gl-form-text> <gl-form-text v-if="description" class="gl-mt-3" data-testid="dropdown-input-description">{{
description
}}</gl-form-text>
</template> </template>
<gl-dropdown :id="field" :text="text" :disabled="disabled"> <gl-dropdown :id="field" :text="text" :disabled="disabled">
<gl-dropdown-section-header
v-if="sectionHeader"
data-testid="dropdown-input-section-header"
>{{ sectionHeader }}</gl-dropdown-section-header
>
<gl-dropdown-item v-for="option in options" :key="option.value" @click="handleInput(option)"> <gl-dropdown-item v-for="option in options" :key="option.value" @click="handleInput(option)">
{{ option.text }} {{ option.text }}
</gl-dropdown-item> </gl-dropdown-item>
......
---
title: Update copies in API fuzzing configuration form
merge_request: 57753
author:
type: changed
---
title: Remove N+1 DB queries from indexing issues in Elasticsearch
merge_request: 57788
author:
type: performance
...@@ -28,6 +28,12 @@ module Elastic ...@@ -28,6 +28,12 @@ module Elastic
search(query_hash, options) search(query_hash, options)
end end
# rubocop: disable CodeReuse/ActiveRecord
def preload_indexing_data(relation)
relation.includes(:issue_assignees, project: [:project_feature])
end
# rubocop: enable CodeReuse/ActiveRecord
private private
# Builds an elasticsearch query that will select documents from a # Builds an elasticsearch query that will select documents from a
......
...@@ -12,7 +12,11 @@ module Elastic ...@@ -12,7 +12,11 @@ module Elastic
data[attr.to_s] = safely_read_attribute_for_elasticsearch(attr) data[attr.to_s] = safely_read_attribute_for_elasticsearch(attr)
end end
data['assignee_id'] = safely_read_attribute_for_elasticsearch(:assignee_ids) # Load them through the issue_assignees table since calling
# assignee_ids can't be easily preloaded and does
# unnecessary joins
data['assignee_id'] = safely_read_attribute_for_elasticsearch(:issue_assignee_user_ids)
data['visibility_level'] = target.project.visibility_level data['visibility_level'] = target.project.visibility_level
data['issues_access_level'] = safely_read_project_feature_for_elasticsearch(:issues) data['issues_access_level'] = safely_read_project_feature_for_elasticsearch(:issues)
......
...@@ -169,11 +169,11 @@ describe('EE - ApiFuzzingConfigurationForm', () => { ...@@ -169,11 +169,11 @@ describe('EE - ApiFuzzingConfigurationForm', () => {
}); });
it('displays a dropdown option for each scan profile', () => { it('displays a dropdown option for each scan profile', () => {
findScanProfileDropdownInput() const dropdownItems = findScanProfileDropdownInput().findAll('li').wrappers;
.findAll('li') dropdownItems.shift(); // Skip section header
.wrappers.forEach((item, index) => { dropdownItems.forEach((item, index) => {
expect(item.text()).toBe(apiFuzzingCiConfiguration.scanProfiles[index].description); expect(item.text()).toBe(apiFuzzingCiConfiguration.scanProfiles[index].description);
}); });
}); });
it('by default, YAML viewer is not visible', () => { it('by default, YAML viewer is not visible', () => {
......
import { GlDropdown, GlLink } from '@gitlab/ui'; import { GlDropdown, GlLink } from '@gitlab/ui';
import { mount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import GlDropdownInput from 'ee/security_configuration/components/dropdown_input.vue'; import GlDropdownInput from 'ee/security_configuration/components/dropdown_input.vue';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
describe('DropdownInput component', () => { describe('DropdownInput component', () => {
let wrapper; let wrapper;
...@@ -26,17 +27,21 @@ describe('DropdownInput component', () => { ...@@ -26,17 +27,21 @@ describe('DropdownInput component', () => {
const newValue = 'foo'; const newValue = 'foo';
const createComponent = ({ props = {} } = {}) => { const createComponent = ({ props = {} } = {}) => {
wrapper = mount(GlDropdownInput, { wrapper = extendedWrapper(
propsData: { mount(GlDropdownInput, {
...props, propsData: {
}, ...props,
}); },
}),
);
}; };
const findToggle = () => wrapper.find('button'); const findToggle = () => wrapper.find('button');
const findLabel = () => wrapper.find('label'); const findLabel = () => wrapper.find('label');
const findDescription = () => wrapper.findByTestId('dropdown-input-description');
const findInputComponent = () => wrapper.find(GlDropdown); const findInputComponent = () => wrapper.find(GlDropdown);
const findRestoreDefaultLink = () => wrapper.find(GlLink); const findRestoreDefaultLink = () => wrapper.find(GlLink);
const findSectionHeader = () => wrapper.findByTestId('dropdown-input-section-header');
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
...@@ -44,18 +49,55 @@ describe('DropdownInput component', () => { ...@@ -44,18 +49,55 @@ describe('DropdownInput component', () => {
}); });
describe('label', () => { describe('label', () => {
beforeEach(() => { describe('with a description', () => {
beforeEach(() => {
createComponent({
props: testProps,
});
});
it('renders the label', () => {
expect(findLabel().text()).toContain(testProps.label);
});
it('renders the description', () => {
const description = findDescription();
expect(description.exists()).toBe(true);
expect(description.text()).toBe(testProps.description);
});
});
describe('without a description', () => {
beforeEach(() => {
createComponent({
props: { ...testProps, description: '' },
});
});
it('does not render the description', () => {
expect(findDescription().exists()).toBe(false);
});
});
});
describe('section header', () => {
it('does not render a section header by default', () => {
createComponent({ createComponent({
props: testProps, props: testProps,
}); });
});
it('renders the label', () => { expect(findSectionHeader().exists()).toBe(false);
expect(findLabel().text()).toContain(testProps.label);
}); });
it('renders the description', () => { it('renders a section header when passed a sectionHeader prop', () => {
expect(findLabel().text()).toContain(testProps.description); const sectionHeader = 'Section header';
createComponent({
props: { ...testProps, sectionHeader },
});
expect(findSectionHeader().exists()).toBe(true);
expect(findSectionHeader().text()).toBe(sectionHeader);
}); });
}); });
......
...@@ -221,7 +221,7 @@ RSpec.describe Elastic::ProcessBookkeepingService, :clean_gitlab_redis_shared_st ...@@ -221,7 +221,7 @@ RSpec.describe Elastic::ProcessBookkeepingService, :clean_gitlab_redis_shared_st
described_class.track!(*notes) described_class.track!(*notes)
control = ActiveRecord::QueryRecorder.new { described_class.new.execute } control = ActiveRecord::QueryRecorder.new(skip_cached: false) { described_class.new.execute }
3.times do 3.times do
notes << create(:note) notes << create(:note)
...@@ -234,6 +234,20 @@ RSpec.describe Elastic::ProcessBookkeepingService, :clean_gitlab_redis_shared_st ...@@ -234,6 +234,20 @@ RSpec.describe Elastic::ProcessBookkeepingService, :clean_gitlab_redis_shared_st
expect { described_class.new.execute }.not_to exceed_all_query_limit(control) expect { described_class.new.execute }.not_to exceed_all_query_limit(control)
end end
it 'does not have N+1 queries for issues' do
issues = create_list(:issue, 2)
described_class.track!(*issues)
control = ActiveRecord::QueryRecorder.new(skip_cached: false) { described_class.new.execute }
issues += create_list(:issue, 3)
described_class.track!(*issues)
expect { described_class.new.execute }.not_to exceed_all_query_limit(control)
end
end end
def expect_processing(*refs, failures: []) def expect_processing(*refs, failures: [])
......
...@@ -1470,13 +1470,19 @@ msgstr "" ...@@ -1470,13 +1470,19 @@ msgstr ""
msgid "API version" msgid "API version"
msgstr "" msgstr ""
msgid "APIFuzzing|API Fuzzing Configuration" msgid "APIFuzzing|$VariableWithPassword"
msgstr ""
msgid "APIFuzzing|$VariableWithUsername"
msgstr "" msgstr ""
msgid "APIFuzzing|Authentication is handled by providing HTTP basic authentication token as a header or cookie. %{linkStart}More information%{linkEnd}." msgid "APIFuzzing|/folder/example_file.json"
msgstr "" msgstr ""
msgid "APIFuzzing|Base URL of API fuzzing target." msgid "APIFuzzing|API Fuzzing Configuration"
msgstr ""
msgid "APIFuzzing|Base URL of API testing target. For example, http://www.example.com."
msgstr "" msgstr ""
msgid "APIFuzzing|Choose a method" msgid "APIFuzzing|Choose a method"
...@@ -1488,6 +1494,9 @@ msgstr "" ...@@ -1488,6 +1494,9 @@ msgstr ""
msgid "APIFuzzing|Code snippet for the API Fuzzing configuration" msgid "APIFuzzing|Code snippet for the API Fuzzing configuration"
msgstr "" msgstr ""
msgid "APIFuzzing|Configure HTTP basic authentication values. Other authentication methods are supported. %{linkStart}Learn more%{linkEnd}."
msgstr ""
msgid "APIFuzzing|Copy code and open .gitlab-ci.yml file" msgid "APIFuzzing|Copy code and open .gitlab-ci.yml file"
msgstr "" msgstr ""
...@@ -1500,10 +1509,10 @@ msgstr "" ...@@ -1500,10 +1509,10 @@ msgstr ""
msgid "APIFuzzing|Enable authentication" msgid "APIFuzzing|Enable authentication"
msgstr "" msgstr ""
msgid "APIFuzzing|Ex: $TestPassword" msgid "APIFuzzing|Enter the name of the variable containing the password. For example, $VariableWithPassword."
msgstr "" msgstr ""
msgid "APIFuzzing|Ex: $TestUsername" msgid "APIFuzzing|Enter the name of the variable containing the username. For example, $VariableWithUsername."
msgstr "" msgstr ""
msgid "APIFuzzing|Ex: Project_Test/File/example_fuzz" msgid "APIFuzzing|Ex: Project_Test/File/example_fuzz"
...@@ -1512,7 +1521,7 @@ msgstr "" ...@@ -1512,7 +1521,7 @@ msgstr ""
msgid "APIFuzzing|Ex: Project_Test/File/example_fuzz.har" msgid "APIFuzzing|Ex: Project_Test/File/example_fuzz.har"
msgstr "" msgstr ""
msgid "APIFuzzing|Ex: Project_Test/File/example_fuzz.json" msgid "APIFuzzing|File path containing APIs to be tested. For example, /folder/example_file.json."
msgstr "" msgstr ""
msgid "APIFuzzing|Generate code snippet" msgid "APIFuzzing|Generate code snippet"
...@@ -1521,12 +1530,6 @@ msgstr "" ...@@ -1521,12 +1530,6 @@ msgstr ""
msgid "APIFuzzing|HAR files may contain sensitive information such as authentication tokens, API keys, and session cookies. We recommend that you review the HAR files' contents before adding them to a repository." msgid "APIFuzzing|HAR files may contain sensitive information such as authentication tokens, API keys, and session cookies. We recommend that you review the HAR files' contents before adding them to a repository."
msgstr "" msgstr ""
msgid "APIFuzzing|Instead of entering the password directly, enter the key of the CI variable set to the password."
msgstr ""
msgid "APIFuzzing|Instead of entering the username directly, enter the key of the CI variable set to the username."
msgstr ""
msgid "APIFuzzing|Make sure your credentials are secured" msgid "APIFuzzing|Make sure your credentials are secured"
msgstr "" msgstr ""
...@@ -1536,6 +1539,9 @@ msgstr "" ...@@ -1536,6 +1539,9 @@ msgstr ""
msgid "APIFuzzing|Postman collections are a group of saved requests you can organize into folders." msgid "APIFuzzing|Postman collections are a group of saved requests you can organize into folders."
msgstr "" msgstr ""
msgid "APIFuzzing|Predefined profiles"
msgstr ""
msgid "APIFuzzing|Scan mode" msgid "APIFuzzing|Scan mode"
msgstr "" msgstr ""
...@@ -1566,9 +1572,6 @@ msgstr "" ...@@ -1566,9 +1572,6 @@ msgstr ""
msgid "APIFuzzing|Username for basic authentication" msgid "APIFuzzing|Username for basic authentication"
msgstr "" msgstr ""
msgid "APIFuzzing|We recommend that you review the JSON specifications file before adding it to a repository."
msgstr ""
msgid "APIFuzzing|You may need a maintainer's help to secure your credentials." msgid "APIFuzzing|You may need a maintainer's help to secure your credentials."
msgstr "" msgstr ""
...@@ -12501,9 +12504,6 @@ msgstr "" ...@@ -12501,9 +12504,6 @@ msgstr ""
msgid "Evidence collection" msgid "Evidence collection"
msgstr "" msgstr ""
msgid "Ex: Example.com"
msgstr ""
msgid "Exactly one of %{attributes} is required" msgid "Exactly one of %{attributes} is required"
msgstr "" msgstr ""
...@@ -21698,12 +21698,6 @@ msgstr "" ...@@ -21698,12 +21698,6 @@ msgstr ""
msgid "Open" msgid "Open"
msgstr "" msgstr ""
msgid "Open API"
msgstr ""
msgid "Open API specification file path"
msgstr ""
msgid "Open Selection" msgid "Open Selection"
msgstr "" msgstr ""
...@@ -21734,6 +21728,12 @@ msgstr "" ...@@ -21734,6 +21728,12 @@ msgstr ""
msgid "Open: %{open}" msgid "Open: %{open}"
msgstr "" msgstr ""
msgid "OpenAPI"
msgstr ""
msgid "OpenAPI specification file path"
msgstr ""
msgid "Opened" msgid "Opened"
msgstr "" msgstr ""
...@@ -36107,6 +36107,9 @@ msgstr "" ...@@ -36107,6 +36107,9 @@ msgstr ""
msgid "http:" msgid "http:"
msgstr "" msgstr ""
msgid "http://www.example.com"
msgstr ""
msgid "https://your-bitbucket-server" msgid "https://your-bitbucket-server"
msgstr "" msgstr ""
......
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