diff options
Diffstat (limited to 'app')
20 files changed, 232 insertions, 31 deletions
diff --git a/app/assets/javascripts/jira_connect/branches/components/new_branch_form.vue b/app/assets/javascripts/jira_connect/branches/components/new_branch_form.vue index 46c27c33f56..1d926c0d0c5 100644 --- a/app/assets/javascripts/jira_connect/branches/components/new_branch_form.vue +++ b/app/assets/javascripts/jira_connect/branches/components/new_branch_form.vue @@ -193,6 +193,7 @@ export default { > <source-branch-dropdown id="source-branch-select" + :key="selectedProject.id" :selected-project="selectedProject" :selected-branch-name="selectedSourceBranchName" @change="onSourceBranchSelect" diff --git a/app/assets/javascripts/jira_connect/branches/components/source_branch_dropdown.vue b/app/assets/javascripts/jira_connect/branches/components/source_branch_dropdown.vue index e443df35ad2..3f9dd4eb6c6 100644 --- a/app/assets/javascripts/jira_connect/branches/components/source_branch_dropdown.vue +++ b/app/assets/javascripts/jira_connect/branches/components/source_branch_dropdown.vue @@ -2,6 +2,8 @@ import { GlCollapsibleListbox } from '@gitlab/ui'; import { debounce } from 'lodash'; import { __ } from '~/locale'; +import { logError } from '~/lib/logger'; + import { BRANCHES_PER_PAGE } from '../constants'; import getProjectQuery from '../graphql/queries/get_project.query.graphql'; @@ -26,6 +28,7 @@ export default { return { initialSourceBranchNamesLoading: false, sourceBranchNamesLoading: false, + sourceBranchNamesLoadingMore: false, sourceBranchNames: [], }; }, @@ -36,6 +39,11 @@ export default { hasSelectedSourceBranch() { return Boolean(this.selectedBranchName); }, + hasMoreBranches() { + return ( + this.sourceBranchNames.length > 0 && this.sourceBranchNames.length % BRANCHES_PER_PAGE === 0 + ); + }, branchDropdownText() { return this.selectedBranchName || __('Select a branch'); }, @@ -59,45 +67,63 @@ export default { onSearch: debounce(function debouncedSearch(branchSearchQuery) { this.onSourceBranchSearchQuery(branchSearchQuery); }, 250), - onSourceBranchSearchQuery(branchSearchQuery) { + async onSourceBranchSearchQuery(branchSearchQuery) { this.branchSearchQuery = branchSearchQuery; - this.fetchSourceBranchNames({ + this.sourceBranchNamesLoading = true; + + await this.fetchSourceBranchNames({ + projectPath: this.selectedProject.fullPath, + searchPattern: this.branchSearchQuery, + }); + this.sourceBranchNamesLoading = false; + }, + async onBottomReached() { + this.sourceBranchNamesLoadingMore = true; + + await this.fetchSourceBranchNames({ projectPath: this.selectedProject.fullPath, searchPattern: this.branchSearchQuery, + append: true, }); + + this.sourceBranchNamesLoadingMore = false; }, onError({ message } = {}) { this.$emit('error', { message }); }, - async fetchSourceBranchNames({ projectPath, searchPattern } = {}) { - this.sourceBranchNamesLoading = true; + async fetchSourceBranchNames({ projectPath, searchPattern, append = false } = {}) { try { const { data } = await this.$apollo.query({ query: getProjectQuery, variables: { projectPath, branchNamesLimit: this.$options.BRANCHES_PER_PAGE, - branchNamesOffset: 0, + branchNamesOffset: append ? this.sourceBranchNames.length : 0, branchNamesSearchPattern: searchPattern ? `*${searchPattern}*` : '*', }, }); const { branchNames, rootRef } = data?.project.repository || {}; - this.sourceBranchNames = - branchNames.map((value) => { + const branchNameItems = + branchNames?.map((value) => { return { text: value, value }; }) || []; - // Use root ref as the default selection - if (rootRef && !this.hasSelectedSourceBranch) { - this.onSourceBranchSelect(rootRef); + if (append) { + this.sourceBranchNames.push(...branchNameItems); + } else { + this.sourceBranchNames = branchNameItems; + + // Use root ref as the default selection + if (rootRef && !this.hasSelectedSourceBranch) { + this.onSourceBranchSelect(rootRef); + } } } catch (err) { + logError(err); this.onError({ message: __('Something went wrong while fetching source branches.'), }); - } finally { - this.sourceBranchNamesLoading = false; } }, }, @@ -107,6 +133,7 @@ export default { <template> <gl-collapsible-listbox :class="{ 'gl-font-monospace': hasSelectedSourceBranch }" + :selected="selectedBranchName" :disabled="!hasSelectedProject" :items="sourceBranchNames" :loading="initialSourceBranchNamesLoading" @@ -114,6 +141,9 @@ export default { :searching="sourceBranchNamesLoading" :toggle-text="branchDropdownText" fluid-width + :infinite-scroll="hasMoreBranches" + :infinite-scroll-loading="sourceBranchNamesLoadingMore" + @bottom-reached="onBottomReached" @search="onSearch" @select="onSourceBranchSelect" /> diff --git a/app/assets/javascripts/jira_connect/branches/index.js b/app/assets/javascripts/jira_connect/branches/index.js index a9a56a6362e..893b9dfa1c7 100644 --- a/app/assets/javascripts/jira_connect/branches/index.js +++ b/app/assets/javascripts/jira_connect/branches/index.js @@ -19,6 +19,7 @@ export default function initJiraConnectBranches() { return new Vue({ el, + name: 'JiraConnectNewBranchRoot', apolloProvider, provide: { initialBranchName, diff --git a/app/assets/javascripts/search/sidebar/components/app.vue b/app/assets/javascripts/search/sidebar/components/app.vue index da743f5c496..a2e857dc5e4 100644 --- a/app/assets/javascripts/search/sidebar/components/app.vue +++ b/app/assets/javascripts/search/sidebar/components/app.vue @@ -13,12 +13,14 @@ import { SCOPE_MERGE_REQUESTS, SCOPE_BLOB, SCOPE_PROJECTS, + SCOPE_NOTES, SEARCH_TYPE_ADVANCED, } from '../constants'; import IssuesFilters from './issues_filters.vue'; import MergeRequestsFilters from './merge_requests_filters.vue'; import BlobsFilters from './blobs_filters.vue'; import ProjectsFilters from './projects_filters.vue'; +import NotesFilters from './notes_filters.vue'; export default { name: 'GlobalSearchSidebar', @@ -27,6 +29,7 @@ export default { MergeRequestsFilters, BlobsFilters, ProjectsFilters, + NotesFilters, ScopeLegacyNavigation, ScopeSidebarNavigation, SidebarPortal, @@ -51,6 +54,13 @@ export default { // for now the feature flag is here. Since we have only one filter in projects scope return this.currentScope === SCOPE_PROJECTS; }, + showNotesFilters() { + return ( + this.currentScope === SCOPE_NOTES && + this.searchType === SEARCH_TYPE_ADVANCED && + this.glFeatures.searchNotesHideArchivedProjects + ); + }, showScopeNavigation() { // showScopeNavigation refers to whether the scope navigation should be shown // while the legacy navigation is being used and there are no search results @@ -75,6 +85,7 @@ export default { <merge-requests-filters v-if="showMergeRequestFilters" /> <blobs-filters v-if="showBlobFilters" /> <projects-filters v-if="showProjectsFilters" /> + <notes-filters v-if="showNotesFilters" /> </sidebar-portal> </section> @@ -88,6 +99,7 @@ export default { <merge-requests-filters v-if="showMergeRequestFilters" /> <blobs-filters v-if="showBlobFilters" /> <projects-filters v-if="showProjectsFilters" /> + <notes-filters v-if="showNotesFilters" /> </div> <small-screen-drawer-navigation class="gl-lg-display-none"> <scope-legacy-navigation /> @@ -95,6 +107,7 @@ export default { <merge-requests-filters v-if="showMergeRequestFilters" /> <blobs-filters v-if="showBlobFilters" /> <projects-filters v-if="showProjectsFilters" /> + <notes-filters v-if="showNotesFilters" /> </small-screen-drawer-navigation> </section> </template> diff --git a/app/assets/javascripts/search/sidebar/components/archived_filter/data.js b/app/assets/javascripts/search/sidebar/components/archived_filter/data.js index 831e253a0b6..7a3dc8ad96b 100644 --- a/app/assets/javascripts/search/sidebar/components/archived_filter/data.js +++ b/app/assets/javascripts/search/sidebar/components/archived_filter/data.js @@ -9,6 +9,7 @@ const scopes = { PROJECTS: 'projects', ISSUES: 'issues', MERGE_REQUESTS: 'merge_requests', + NOTES: 'notes', }; const filterParam = 'include_archived'; diff --git a/app/assets/javascripts/search/sidebar/components/notes_filters.vue b/app/assets/javascripts/search/sidebar/components/notes_filters.vue new file mode 100644 index 00000000000..3a9f582d554 --- /dev/null +++ b/app/assets/javascripts/search/sidebar/components/notes_filters.vue @@ -0,0 +1,18 @@ +<script> +import ArchivedFilter from './archived_filter/index.vue'; +import FiltersTemplate from './filters_template.vue'; + +export default { + name: 'NotesFilters', + components: { + ArchivedFilter, + FiltersTemplate, + }, +}; +</script> + +<template> + <filters-template> + <archived-filter class="gl-mb-5" /> + </filters-template> +</template> diff --git a/app/assets/javascripts/search/sidebar/constants/index.js b/app/assets/javascripts/search/sidebar/constants/index.js index e5b803b92cb..e6808082185 100644 --- a/app/assets/javascripts/search/sidebar/constants/index.js +++ b/app/assets/javascripts/search/sidebar/constants/index.js @@ -2,6 +2,7 @@ export const SCOPE_ISSUES = 'issues'; export const SCOPE_MERGE_REQUESTS = 'merge_requests'; export const SCOPE_BLOB = 'blobs'; export const SCOPE_PROJECTS = 'projects'; +export const SCOPE_NOTES = 'notes'; export const LABEL_DEFAULT_CLASSES = [ 'gl-display-flex', 'gl-flex-direction-row', diff --git a/app/assets/javascripts/security_configuration/components/constants.js b/app/assets/javascripts/security_configuration/components/constants.js index fd713a7a504..da213b0ed43 100644 --- a/app/assets/javascripts/security_configuration/components/constants.js +++ b/app/assets/javascripts/security_configuration/components/constants.js @@ -1,5 +1,6 @@ import { helpPagePath } from '~/helpers/help_page_helper'; import { __, s__ } from '~/locale'; +import ContinuousVulnerabilityScan from '~/security_configuration/components/continuous_vulnerability_scan.vue'; import { REPORT_TYPE_SAST, @@ -210,6 +211,7 @@ export const securityFeatures = [ configurationHelpPath: DEPENDENCY_SCANNING_CONFIG_HELP_PATH, type: REPORT_TYPE_DEPENDENCY_SCANNING, anchor: 'dependency-scanning', + slotComponent: ContinuousVulnerabilityScan, }, { name: CONTAINER_SCANNING_NAME, diff --git a/app/assets/javascripts/security_configuration/components/continuous_vulnerability_scan.vue b/app/assets/javascripts/security_configuration/components/continuous_vulnerability_scan.vue new file mode 100644 index 00000000000..61cbde2107c --- /dev/null +++ b/app/assets/javascripts/security_configuration/components/continuous_vulnerability_scan.vue @@ -0,0 +1,127 @@ +<script> +import { GlBadge, GlIcon, GlToggle, GlLink, GlSprintf, GlAlert } from '@gitlab/ui'; +import ProjectSetContinuousVulnerabilityScanning from '~/security_configuration/graphql/project_set_continuous_vulnerability_scanning.graphql'; +import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; +import { __, s__ } from '~/locale'; +import { helpPagePath } from '~/helpers/help_page_helper'; + +export default { + name: 'ContinuousVulnerabilityscan', + components: { GlBadge, GlIcon, GlToggle, GlLink, GlSprintf, GlAlert }, + mixins: [glFeatureFlagsMixin()], + inject: ['continuousVulnerabilityScansEnabled', 'projectFullPath'], + i18n: { + badgeLabel: __('Experiment'), + title: s__('CVS|Continuous Vulnerability Scan'), + description: s__( + 'CVS|Detect vulnerabilities outside a pipeline as new data is added to the GitLab Advisory Database.', + ), + learnMore: __('Learn more'), + testingAgreementMessage: s__( + 'CVS|By enabling this feature, you accept the %{linkStart}Testing Terms of Use%{linkEnd}', + ), + }, + props: { + feature: { + type: Object, + required: true, + }, + }, + data() { + return { + toggleValue: this.continuousVulnerabilityScansEnabled, + errorMessage: '', + isAlertDismissed: false, + }; + }, + computed: { + isFeatureConfigured() { + return this.feature.available && this.feature.configured; + }, + shouldShowAlert() { + return this.errorMessage && !this.isAlertDismissed; + }, + }, + methods: { + reportError(error) { + this.errorMessage = error; + this.isAlertDismissed = false; + }, + async toggleCVS(checked) { + try { + const { data } = await this.$apollo.mutate({ + mutation: ProjectSetContinuousVulnerabilityScanning, + variables: { + input: { + projectPath: this.projectFullPath, + enable: checked, + }, + }, + }); + + const { errors } = data.projectSetContinuousVulnerabilityScanning; + + if (errors.length > 0) { + this.reportError(errors[0].message); + } + if (data.projectSetContinuousVulnerabilityScanning !== null) { + this.toggleValue = checked; + } + } catch (error) { + this.reportError(error); + } + }, + }, + CVSHelpPagePath: helpPagePath( + 'user/application_security/continuous_vulnerability_scanning/index', + ), + experimentHelpPagePath: helpPagePath('policy/experiment-beta-support', { anchor: 'experiment' }), +}; +</script> + +<template> + <div v-if="glFeatures.dependencyScanningOnAdvisoryIngestion"> + <h4 class="gl-font-base gl-m-0 gl-mt-6"> + {{ $options.i18n.title }} + <gl-badge + ref="badge" + :href="$options.experimentHelpPagePath" + target="_blank" + size="sm" + variant="neutral" + class="gl-cursor-pointer" + >{{ $options.i18n.badgeLabel }}</gl-badge + > + </h4> + <gl-alert + v-if="shouldShowAlert" + class="gl-mb-5 gl-mt-2" + variant="danger" + @dismiss="isAlertDismissed = true" + >{{ errorMessage }}</gl-alert + > + <gl-toggle + class="gl-mt-5" + :disabled="!isFeatureConfigured" + :value="toggleValue" + :label="s__('CVS|Toggle CVS')" + label-position="hidden" + @change="toggleCVS" + /> + + <p class="gl-mb-0 gl-mt-5"> + {{ $options.i18n.description }} + <gl-link :href="$options.CVSHelpPagePath" target="_blank">{{ + $options.i18n.learnMore + }}</gl-link> + <br /> + <gl-sprintf :message="$options.i18n.testingAgreementMessage"> + <template #link="{ content }"> + <gl-link href="https://about.gitlab.com/handbook/legal/testing-agreement" target="_blank"> + {{ content }} <gl-icon name="external-link" /> + </gl-link> + </template> + </gl-sprintf> + </p> + </div> +</template> diff --git a/app/assets/javascripts/security_configuration/components/feature_card.vue b/app/assets/javascripts/security_configuration/components/feature_card.vue index a757657339b..7f0a049a6ad 100644 --- a/app/assets/javascripts/security_configuration/components/feature_card.vue +++ b/app/assets/javascripts/security_configuration/components/feature_card.vue @@ -73,6 +73,9 @@ export default { hasSecondary() { return Boolean(this.feature.secondary); }, + hasSlotComponent() { + return Boolean(this.feature.slotComponent); + }, // This condition is a temporary hack to not display any wrong information // until this BE Bug is fixed: https://gitlab.com/gitlab-org/gitlab/-/issues/350307. // More Information: https://gitlab.com/gitlab-org/gitlab/-/issues/350307#note_825447417 @@ -215,5 +218,9 @@ export default { {{ $options.i18n.configurationGuide }} </gl-button> </div> + + <div v-if="hasSlotComponent"> + <component :is="feature.slotComponent" :feature="feature" /> + </div> </gl-card> </template> diff --git a/app/assets/javascripts/security_configuration/graphql/project_set_continuous_vulnerability_scanning.graphql b/app/assets/javascripts/security_configuration/graphql/project_set_continuous_vulnerability_scanning.graphql new file mode 100644 index 00000000000..79f4316d106 --- /dev/null +++ b/app/assets/javascripts/security_configuration/graphql/project_set_continuous_vulnerability_scanning.graphql @@ -0,0 +1,8 @@ +mutation ProjectSetContinuousVulnerabilityScanning( + $input: ProjectSetContinuousVulnerabilityScanningInput! +) { + projectSetContinuousVulnerabilityScanning(input: $input) { + continuousVulnerabilityScanningEnabled + errors + } +} diff --git a/app/assets/javascripts/security_configuration/index.js b/app/assets/javascripts/security_configuration/index.js index aa3c9c87622..4b498091134 100644 --- a/app/assets/javascripts/security_configuration/index.js +++ b/app/assets/javascripts/security_configuration/index.js @@ -26,6 +26,7 @@ export const initSecurityConfiguration = (el) => { autoDevopsHelpPagePath, autoDevopsPath, vulnerabilityTrainingDocsPath, + continuousVulnerabilityScansEnabled, } = el.dataset; const { augmentedSecurityFeatures } = augmentFeatures( @@ -43,6 +44,7 @@ export const initSecurityConfiguration = (el) => { autoDevopsHelpPagePath, autoDevopsPath, vulnerabilityTrainingDocsPath, + continuousVulnerabilityScansEnabled, }, render(createElement) { return createElement(SecurityConfigurationApp, { diff --git a/app/assets/javascripts/super_sidebar/components/nav_item.vue b/app/assets/javascripts/super_sidebar/components/nav_item.vue index 8320da30ba8..7f5cce8341f 100644 --- a/app/assets/javascripts/super_sidebar/components/nav_item.vue +++ b/app/assets/javascripts/super_sidebar/components/nav_item.vue @@ -217,7 +217,7 @@ export default { </div> </div> <slot name="actions"></slot> - <span v-if="hasPill" class="gl-text-right gl-relative gl-min-w-8"> + <span v-if="hasPill || isPinnable" class="gl-text-right gl-relative gl-min-w-8"> <gl-badge v-if="hasPill" size="sm" diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 394ba3c51bf..2abb8e4be48 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -414,9 +414,7 @@ module Ci end def options_scheduled_at - ChronicDuration.parse( - options[:start_in], use_complete_matcher: Feature.enabled?(:update_chronic_duration) - )&.seconds&.from_now + ChronicDuration.parse(options[:start_in], use_complete_matcher: true)&.seconds&.from_now end def action? @@ -740,9 +738,7 @@ module Ci def artifacts_expire_in=(value) self.artifacts_expire_at = if value - ChronicDuration.parse( - value, use_complete_matcher: Feature.enabled?(:update_chronic_duration) - )&.seconds&.from_now + ChronicDuration.parse(value, use_complete_matcher: true)&.seconds&.from_now end end diff --git a/app/models/concerns/chronic_duration_attribute.rb b/app/models/concerns/chronic_duration_attribute.rb index 944ad252cc6..7b7b61fdf06 100644 --- a/app/models/concerns/chronic_duration_attribute.rb +++ b/app/models/concerns/chronic_duration_attribute.rb @@ -18,9 +18,7 @@ module ChronicDurationAttribute begin new_value = if value.present? - ChronicDuration.parse( - value, use_complete_matcher: Feature.enabled?(:update_chronic_duration) - ).to_i + ChronicDuration.parse(value, use_complete_matcher: true).to_i else parameters[:default].presence end diff --git a/app/models/container_expiration_policy.rb b/app/models/container_expiration_policy.rb index 857b7eccf70..f643fa7730b 100644 --- a/app/models/container_expiration_policy.rb +++ b/app/models/container_expiration_policy.rb @@ -80,9 +80,7 @@ class ContainerExpirationPolicy < ApplicationRecord end def set_next_run_at - cadence_seconds = ChronicDuration.parse( - cadence, use_complete_matcher: Feature.enabled?(:update_chronic_duration) - ).seconds + cadence_seconds = ChronicDuration.parse(cadence, use_complete_matcher: true).seconds self.next_run_at = Time.zone.now + cadence_seconds end diff --git a/app/services/projects/container_repository/cleanup_tags_base_service.rb b/app/services/projects/container_repository/cleanup_tags_base_service.rb index 185895698af..61b09de1643 100644 --- a/app/services/projects/container_repository/cleanup_tags_base_service.rb +++ b/app/services/projects/container_repository/cleanup_tags_base_service.rb @@ -100,9 +100,7 @@ module Projects def older_than_in_seconds strong_memoize(:older_than_in_seconds) do - ChronicDuration.parse( - older_than, use_complete_matcher: Feature.enabled?(:update_chronic_duration) - ).seconds + ChronicDuration.parse(older_than, use_complete_matcher: true).seconds end end end diff --git a/app/validators/duration_validator.rb b/app/validators/duration_validator.rb index 5d7eba09122..bcdcf665cba 100644 --- a/app/validators/duration_validator.rb +++ b/app/validators/duration_validator.rb @@ -12,7 +12,7 @@ # class DurationValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) - ChronicDuration.parse(value, use_complete_matcher: Feature.enabled?(:update_chronic_duration)) + ChronicDuration.parse(value, use_complete_matcher: true) rescue ChronicDuration::DurationParseError if options[:message] record.errors.add(:base, options[:message]) diff --git a/app/views/dashboard/todos/_todo.html.haml b/app/views/dashboard/todos/_todo.html.haml index 1cd8015934e..6877e3456ee 100644 --- a/app/views/dashboard/todos/_todo.html.haml +++ b/app/views/dashboard/todos/_todo.html.haml @@ -5,7 +5,7 @@ = todo_target_state_pill(todo) - %span.todo-target-title{ data: { qa_selector: "todo_target_title_content" }, :id => dom_id(todo) + "_describer" } + %span.todo-target-title{ :id => dom_id(todo) + "_describer" } = todo_target_title(todo) - if !todo.for_design? && !todo.member_access_requested? diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml index 4a7aa9a86ab..066b2ef98dd 100644 --- a/app/views/projects/merge_requests/_merge_request.html.haml +++ b/app/views/projects/merge_requests/_merge_request.html.haml @@ -13,7 +13,7 @@ .merge-request-title.title %span.merge-request-title-text.js-onboarding-mr-item = hidden_merge_request_icon(merge_request) - = link_to merge_request.title, merge_request_path(merge_request), class: 'js-prefetch-document' + = link_to markdown_field(merge_request, :title), merge_request_path(merge_request), class: 'js-prefetch-document' - if merge_request.tasks? %span.task-status.d-none.d-sm-inline-block.gl-font-sm |