diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index 908da71a8e4fed8dba7b21b11b1a07f73b102026..5a655c92e46b70da0b83f90cccef1c32c2bb7324 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -83,8 +83,9 @@ def autocomplete @project = search_service.project @ref = params[:project_ref] if params[:project_ref].present? + @filter = params[:filter] - render json: search_autocomplete_opts(term).to_json + render json: search_autocomplete_opts(term, filter: @filter).to_json end def opensearch diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index ecbcaec27bcf0f5f22de8cd1603689cc972909a6..6e2e0a86446bd1ed84fe46ea66c4ea510481f0fc 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -14,28 +14,42 @@ module SearchHelper :project_ids ].freeze - def search_autocomplete_opts(term) + def search_autocomplete_opts(term, filter: nil) return unless current_user - resources_results = [ - recent_items_autocomplete(term), + results = case filter&.to_sym + when :search + resource_results(term) + when :generic + [ + generic_results(term), + recent_items_autocomplete(term) + ] + else + [ + generic_results(term), + resource_results(term), + recent_items_autocomplete(term) + ] + end + + results.flatten { |item| item[:label] } + end + + def resource_results(term) + [ groups_autocomplete(term), projects_autocomplete(term), issue_autocomplete(term) ].flatten + end + def generic_results(term) search_pattern = Regexp.new(Regexp.escape(term), "i") generic_results = project_autocomplete + default_autocomplete + help_autocomplete generic_results.concat(default_autocomplete_admin) if current_user.admin? - generic_results.select! { |result| result[:label] =~ search_pattern } - - [ - resources_results, - generic_results - ].flatten do |item| - item[:label] - end + generic_results.select { |result| result[:label] =~ search_pattern } end def recent_items_autocomplete(term) diff --git a/app/workers/post_receive.rb b/app/workers/post_receive.rb index 68a0934e2b7c5f2d2419972dcfa6023f722c5367..329ccfc63627168ececafb1c483ceb98a469f5d5 100644 --- a/app/workers/post_receive.rb +++ b/app/workers/post_receive.rb @@ -85,6 +85,7 @@ def process_snippet_changes(post_received, snippet) replicate_snippet_changes(snippet) expire_caches(post_received, snippet.repository) + snippet.touch Snippets::UpdateStatisticsService.new(snippet).execute end diff --git a/ee/app/assets/javascripts/security_configuration/dast/components/configuration_form.vue b/ee/app/assets/javascripts/security_configuration/dast/components/configuration_form.vue index 6ff3c730c7df7b746eb4014740deff60e70ee1f0..39f198464a73af432254898242c68fae923cc848 100644 --- a/ee/app/assets/javascripts/security_configuration/dast/components/configuration_form.vue +++ b/ee/app/assets/javascripts/security_configuration/dast/components/configuration_form.vue @@ -36,6 +36,8 @@ export default { 'projectPath', 'siteProfilesLibraryPath', 'scannerProfilesLibraryPath', + 'scannerProfile', + 'siteProfile', ], i18n: { dastConfigurationHeader: s__('DastConfig|DAST CI/CD configuration'), @@ -95,6 +97,8 @@ export default { class="gl-mb-6" :full-path="projectPath" :scanner-profiles-library-path="scannerProfilesLibraryPath" + :saved-scanner-profile-name="scannerProfile" + :saved-site-profile-name="siteProfile" :site-profiles-library-path="siteProfilesLibraryPath" @error="showErrors" @profiles-selected="updateProfiles" diff --git a/ee/app/assets/javascripts/security_configuration/dast/index.js b/ee/app/assets/javascripts/security_configuration/dast/index.js index d6752e4c280fe646761d3d0c9364d7d327f2bf1f..ad16e9091aef561b27751c937ce081c5a20444fb 100644 --- a/ee/app/assets/javascripts/security_configuration/dast/index.js +++ b/ee/app/assets/javascripts/security_configuration/dast/index.js @@ -24,6 +24,8 @@ export default function init() { scannerProfilesLibraryPath, newScannerProfilePath, newSiteProfilePath, + scannerProfile, + siteProfile, } = el.dataset; return new Vue({ @@ -37,6 +39,8 @@ export default function init() { scannerProfilesLibraryPath, newScannerProfilePath, newSiteProfilePath, + scannerProfile, + siteProfile, }, render(createElement) { return createElement(DastConfigurationApp); diff --git a/ee/app/assets/javascripts/security_configuration/dast_profiles/dast_profiles_configurator/dast_profiles_configurator.vue b/ee/app/assets/javascripts/security_configuration/dast_profiles/dast_profiles_configurator/dast_profiles_configurator.vue index fe846639349536dc5e8ecc1600d0e72b31173e4c..3bac20f105eaea2a2eb138a59ccce6128dcfab25 100644 --- a/ee/app/assets/javascripts/security_configuration/dast_profiles/dast_profiles_configurator/dast_profiles_configurator.vue +++ b/ee/app/assets/javascripts/security_configuration/dast_profiles/dast_profiles_configurator/dast_profiles_configurator.vue @@ -23,7 +23,7 @@ import { SITE_PROFILES_QUERY, } from 'ee/on_demand_scans_form/settings'; -const createProfilesApolloOptions = (name, field, { fetchQuery, fetchError }) => ({ +const createProfilesApolloOptions = (name, field, savedField, { fetchQuery, fetchError }) => ({ query: fetchQuery, variables() { return { @@ -35,6 +35,11 @@ const createProfilesApolloOptions = (name, field, { fetchQuery, fetchError }) => if (nodes.length === 1) { this[field] = nodes[0].id; } + + if (this[savedField] && nodes.length > 1) { + this[field] = this.findSavedProfileId(nodes, this[savedField]); + } + return nodes; }, error(e) { @@ -68,11 +73,13 @@ export default { scannerProfiles: createProfilesApolloOptions( 'scannerProfiles', 'selectedScannerProfileId', + 'savedScannerProfileName', SCANNER_PROFILES_QUERY, ), siteProfiles: createProfilesApolloOptions( 'siteProfiles', 'selectedSiteProfileId', + 'savedSiteProfileName', SITE_PROFILES_QUERY, ), }, @@ -87,6 +94,16 @@ export default { required: false, default: null, }, + savedScannerProfileName: { + type: String, + required: false, + default: null, + }, + savedSiteProfileName: { + type: String, + required: false, + default: null, + }, fullPath: { type: String, required: false, @@ -133,10 +150,14 @@ export default { return this.isScannerProfile ? this.savedScannerProfileId : this.savedSiteProfileId; }, savedScannerProfileId() { - return this.savedProfiles?.dastScannerProfile.id; + return this.savedScannerProfileName + ? this.findSavedProfileId(this.scannerProfiles, this.savedScannerProfileName) + : this.savedProfiles?.dastScannerProfile.id; }, savedSiteProfileId() { - return this.savedProfiles?.dastSiteProfile.id; + return this.savedSiteProfileName + ? this.findSavedProfileId(this.siteProfiles, this.savedSiteProfileName) + : this.savedProfiles?.dastSiteProfile.id; }, selectedScannerProfile() { return this.selectedScannerProfileId @@ -164,7 +185,6 @@ export default { }, created() { const params = queryToObject(window.location.search, { legacySpacesDecode: true }); - this.selectedSiteProfileId = params.site_profile_id ? convertToGraphQLId(TYPE_SITE_PROFILE, params.site_profile_id) : this.selectedSiteProfileId; @@ -173,6 +193,9 @@ export default { : this.selectedScannerProfileId; }, methods: { + findSavedProfileId(profiles, name) { + return profiles.find(({ profileName }) => name === profileName)?.id || null; + }, enableEditingMode(type) { this.selectActiveProfile(type); this.openProfileDrawer({ profileType: type, mode: SIDEBAR_VIEW_MODE.EDITING_MODE }); diff --git a/ee/app/assets/javascripts/vulnerabilities/components/generic_report/report_section.vue b/ee/app/assets/javascripts/vulnerabilities/components/generic_report/report_section.vue index e12417b7c4f0282b7b143da5ba45bbe2513312ce..db93f5b8ddd68a4a032a973a57afd8ab94202e4e 100644 --- a/ee/app/assets/javascripts/vulnerabilities/components/generic_report/report_section.vue +++ b/ee/app/assets/javascripts/vulnerabilities/components/generic_report/report_section.vue @@ -59,14 +59,17 @@ export default { </header> <gl-collapse :visible="showSection"> <div class="generic-report-container" data-testid="reports"> - <template v-for="[label, item] in detailsEntries"> - <div :key="label" class="generic-report-row" :data-testid="`report-row-${label}`"> - <strong class="generic-report-column">{{ item.name || label }}</strong> - <div class="generic-report-column" data-testid="reportContent"> - <report-item :item="item" :data-testid="`report-item-${label}`" /> - </div> + <div + v-for="[label, item] in detailsEntries" + :key="label" + class="generic-report-row" + :data-testid="`report-row-${label}`" + > + <strong class="generic-report-column">{{ item.name || label }}</strong> + <div class="generic-report-column" data-testid="reportContent"> + <report-item :item="item" :data-testid="`report-item-${label}`" /> </div> - </template> + </div> </div> </gl-collapse> </section> diff --git a/ee/spec/frontend/security_configuration/dast/components/configuration_form_spec.js b/ee/spec/frontend/security_configuration/dast/components/configuration_form_spec.js index 77bd433b0c81945a7d721fad59fda865eff97e44..d31a4f71e9c14961223e7e7c42468e695b9992a4 100644 --- a/ee/spec/frontend/security_configuration/dast/components/configuration_form_spec.js +++ b/ee/spec/frontend/security_configuration/dast/components/configuration_form_spec.js @@ -21,6 +21,8 @@ const gitlabCiYamlEditPath = '/ci/editor'; const projectPath = '/project/path'; const siteProfilesLibraryPath = 'siteProfilesLibraryPath'; const scannerProfilesLibraryPath = 'scannerProfilesLibraryPath'; +const savedScannerProfile = 'scannerProfile'; +const savedSiteProfile = 'siteProfile'; const selectedScannerProfileName = 'My Scan profile'; const selectedSiteProfileName = 'My site profile'; @@ -80,6 +82,8 @@ describe('EE - DAST Configuration Form', () => { scannerProfilesLibraryPath, siteProfilesLibraryPath, projectPath, + scannerProfile: savedScannerProfile, + siteProfile: savedSiteProfile, ...glFeatures, }, stubs: { diff --git a/ee/spec/frontend/security_configuration/dast_profiles/dast_profiles_configurator/dast_profiles_configurator_spec.js b/ee/spec/frontend/security_configuration/dast_profiles/dast_profiles_configurator/dast_profiles_configurator_spec.js index 0776066a2141f4fe3d120c68222e982887132d0a..bf5acae678fbe80461844a3563260fa20e83d3ab 100644 --- a/ee/spec/frontend/security_configuration/dast_profiles/dast_profiles_configurator/dast_profiles_configurator_spec.js +++ b/ee/spec/frontend/security_configuration/dast_profiles/dast_profiles_configurator/dast_profiles_configurator_spec.js @@ -49,6 +49,7 @@ describe('DastProfilesConfigurator', () => { const findNewScanButton = () => wrapper.findByTestId('new-profile-button'); const findOpenDrawerButton = () => wrapper.findByTestId('select-profile-action-btn'); const findCancelButton = () => wrapper.findByTestId('dast-profile-form-cancel-button'); + const findInUseLabel = () => wrapper.findByTestId('in-use-label'); const findSectionLayout = () => wrapper.findComponent(SectionLayout); const findSectionLoader = () => wrapper.findComponent(SectionLoader); const findScannerProfilesSelector = () => wrapper.findComponent(ScannerProfileSelector); @@ -59,6 +60,11 @@ describe('DastProfilesConfigurator', () => { const findDastProfilesSidebarList = () => wrapper.findComponent(DastProfilesSidebarList); const findDastProfilesSidebarForm = () => wrapper.findComponent(DastProfilesSidebarForm); + const openDrawer = async () => { + findOpenDrawerButton().vm.$emit('click'); + await nextTick(); + }; + const createComponentFactory = (mountFn = shallowMount) => (options = {}, withHandlers) => { localVue = createLocalVue(); let defaultMocks = { @@ -203,16 +209,31 @@ describe('DastProfilesConfigurator', () => { }); }); + describe('saved profile names', () => { + const { profileName: savedScannerProfileName } = scannerProfiles[0]; + const { profileName: savedSiteProfileName } = siteProfiles[0]; + + beforeEach(async () => { + createComponent({ savedSiteProfileName, savedScannerProfileName }, true); + await nextTick(); + }); + + it('should have saved profiles selected', async () => { + expect(findScannerProfilesSelector().find('h3').text()).toContain(savedScannerProfileName); + expect(findSiteProfilesSelector().find('h3').text()).toContain(savedSiteProfileName); + }); + + it('should mark saved profiles as in-use', async () => { + await openDrawer(); + expect(findInUseLabel().exists()).toBe(true); + }); + }); + describe('switching between modes', () => { beforeEach(() => { createComponent(); }); - const openDrawer = async () => { - findOpenDrawerButton().vm.$emit('click'); - await nextTick(); - }; - const expectEditingMode = async () => { findNewScanButton().vm.$emit('click'); await nextTick(); diff --git a/ee/spec/helpers/search_helper_spec.rb b/ee/spec/helpers/search_helper_spec.rb index 13f845f658271a783ff066ee60438d7b123a4688..d7b4f57b6308a036f614fb6e158ecae59edd130c 100644 --- a/ee/spec/helpers/search_helper_spec.rb +++ b/ee/spec/helpers/search_helper_spec.rb @@ -140,6 +140,34 @@ avatar_url: '' # This group didn't have an avatar so set this to '' }) end + + context 'with the filter parameter is present' do + it 'filter is set to search' do + project = create(:project, title: 'hello world') + project.add_developer(user) + + results = search_autocomplete_opts('hello', filter: 'search') + expect(results.count).to eq(1) + + expect(results.first).to include({ + category: 'Projects', + id: project.id, + label: project.full_name, + url: Gitlab::Routing.url_helpers.project_path(project) + }) + end + + it 'filter is set to generic' do + results = search_autocomplete_opts('setting', filter: 'generic') + expect(results.count).to eq(1) + + expect(results.first).to include({ + category: 'Settings', + label: 'User settings', + url: Gitlab::Routing.url_helpers.profile_path + }) + end + end end end diff --git a/spec/controllers/search_controller_spec.rb b/spec/controllers/search_controller_spec.rb index 6922b0f38312211d93f1e2adb2edf865b1042f2b..e03a8b7d321ec8f2d2f9f9e33539cef9e9a7edaf 100644 --- a/spec/controllers/search_controller_spec.rb +++ b/spec/controllers/search_controller_spec.rb @@ -393,6 +393,13 @@ def request get(:autocomplete, params: { term: 'foo@bar.com', scope: 'users' }) end end + + it 'can be filtered with params[:filter]' do + get :autocomplete, params: { term: 'setting', filter: 'generic' } + expect(response).to have_gitlab_http_status(:ok) + expect(json_response.count).to eq(1) + expect(json_response.first['label']).to match(/User settings/) + end end describe '#append_info_to_payload' do diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb index 4ddb793516ff9a25d3609b74f682cf3cab87744f..d632ca39e448e2d6a2a7dd7f99a8da9fec885975 100644 --- a/spec/workers/post_receive_spec.rb +++ b/spec/workers/post_receive_spec.rb @@ -452,6 +452,12 @@ def perform(changes: base64_changes) perform end + it 'updates the snippet model updated_at' do + expect(snippet).to receive(:touch) + + perform + end + it 'updates snippet statistics' do expect(Snippets::UpdateStatisticsService).to receive(:new).with(snippet).and_call_original