diff options
Diffstat (limited to 'app')
41 files changed, 180 insertions, 237 deletions
diff --git a/app/assets/javascripts/environments/mount_show.js b/app/assets/javascripts/environments/mount_show.js index 364f68cefb7..cc13a237aca 100644 --- a/app/assets/javascripts/environments/mount_show.js +++ b/app/assets/javascripts/environments/mount_show.js @@ -94,7 +94,7 @@ export const initPage = async () => { router, provide: { projectPath: dataSet.projectFullPath, - graphqlEtagKey: dataSet.graphqlEtagPath, + graphqlEtagKey: dataSet.graphqlEtagKey, }, render(createElement) { return createElement('router-view'); diff --git a/app/assets/javascripts/graphql_shared/utils.js b/app/assets/javascripts/graphql_shared/utils.js index 198d9f980f0..6a64e8a2fa8 100644 --- a/app/assets/javascripts/graphql_shared/utils.js +++ b/app/assets/javascripts/graphql_shared/utils.js @@ -16,7 +16,10 @@ export const isGid = (id) => { return false; }; -const parseGid = (gid) => parseInt(`${gid}`.replace(/gid:\/\/gitlab\/.*\//g, ''), 10); +const parseGid = (gid) => { + const [type, id] = `${gid}`.replace(/gid:\/\/gitlab\//g, '').split('/'); + return { type, id }; +}; /** * Ids generated by GraphQL endpoints are usually in the format @@ -27,8 +30,24 @@ const parseGid = (gid) => parseInt(`${gid}`.replace(/gid:\/\/gitlab\/.*\//g, '') * @returns {Number} */ export const getIdFromGraphQLId = (gid = '') => { - const parsedGid = parseGid(gid); - return Number.isInteger(parsedGid) ? parsedGid : null; + const rawId = isGid(gid) ? parseGid(gid).id : gid; + const id = parseInt(rawId, 10); + return Number.isInteger(id) ? id : null; +}; + +/** + * Ids generated by GraphQL endpoints are usually in the format + * gid://gitlab/Environments/123. This method extracts Type string + * from the Id path + * + * @param {String} gid GraphQL global ID + * @returns {String} + */ +export const getTypeFromGraphQLId = (gid = '') => { + if (!isGid(gid)) return null; + + const { type } = parseGid(gid); + return type || null; }; export const MutationOperationMode = { diff --git a/app/assets/javascripts/security_configuration/components/app.vue b/app/assets/javascripts/security_configuration/components/app.vue index 66b8db1f764..d57b3fda342 100644 --- a/app/assets/javascripts/security_configuration/components/app.vue +++ b/app/assets/javascripts/security_configuration/components/app.vue @@ -12,7 +12,6 @@ import FeatureCard from './feature_card.vue'; import TrainingProviderList from './training_provider_list.vue'; export const i18n = { - compliance: s__('SecurityConfiguration|Compliance'), configurationHistory: s__('SecurityConfiguration|Configuration history'), securityTesting: s__('SecurityConfiguration|Security testing'), latestPipelineDescription: s__( @@ -59,10 +58,6 @@ export default { type: Array, required: true, }, - augmentedComplianceFeatures: { - type: Array, - required: true, - }, gitlabCiPresent: { type: Boolean, required: false, @@ -101,9 +96,7 @@ export default { }, computed: { canUpgrade() { - return [...this.augmentedSecurityFeatures, ...this.augmentedComplianceFeatures].some( - ({ available }) => !available, - ); + return [...this.augmentedSecurityFeatures].some(({ available }) => !available); }, canViewCiHistory() { return Boolean(this.gitlabCiPresent && this.gitlabCiHistoryPath); @@ -226,44 +219,6 @@ export default { </section-layout> </gl-tab> <gl-tab - data-testid="compliance-testing-tab" - :title="$options.i18n.compliance" - query-param-value="compliance-testing" - > - <section-layout :heading="$options.i18n.compliance"> - <template #description> - <p> - <span data-testid="latest-pipeline-info-compliance"> - <gl-sprintf - v-if="latestPipelinePath" - :message="$options.i18n.latestPipelineDescription" - > - <template #link="{ content }"> - <gl-link :href="latestPipelinePath">{{ content }}</gl-link> - </template> - </gl-sprintf> - </span> - - {{ $options.i18n.description }} - </p> - <p v-if="canViewCiHistory"> - <gl-link data-testid="compliance-view-history-link" :href="gitlabCiHistoryPath">{{ - $options.i18n.configurationHistory - }}</gl-link> - </p> - </template> - <template #features> - <feature-card - v-for="feature in augmentedComplianceFeatures" - :key="feature.type" - :feature="feature" - class="gl-mb-6" - @error="onError" - /> - </template> - </section-layout> - </gl-tab> - <gl-tab data-testid="vulnerability-management-tab" :title="$options.i18n.vulnerabilityManagement" query-param-value="vulnerability-management" diff --git a/app/assets/javascripts/security_configuration/components/constants.js b/app/assets/javascripts/security_configuration/components/constants.js index d46e9983a44..3bf0401ef5e 100644 --- a/app/assets/javascripts/security_configuration/components/constants.js +++ b/app/assets/javascripts/security_configuration/components/constants.js @@ -13,7 +13,6 @@ import { REPORT_TYPE_COVERAGE_FUZZING, REPORT_TYPE_CORPUS_MANAGEMENT, REPORT_TYPE_API_FUZZING, - REPORT_TYPE_LICENSE_COMPLIANCE, } from '~/vue_shared/security_reports/constants'; import kontraLogo from 'images/vulnerability/kontra-logo.svg'; @@ -150,14 +149,6 @@ export const API_FUZZING_NAME = __('API Fuzzing'); export const API_FUZZING_DESCRIPTION = __('Find bugs in your code with API fuzzing.'); export const API_FUZZING_HELP_PATH = helpPagePath('user/application_security/api_fuzzing/index'); -export const LICENSE_COMPLIANCE_NAME = __('License Compliance'); -export const LICENSE_COMPLIANCE_DESCRIPTION = __( - 'Search your project dependencies for their licenses and apply policies.', -); -export const LICENSE_COMPLIANCE_HELP_PATH = helpPagePath( - 'user/compliance/license_compliance/index', -); - export const CLUSTER_IMAGE_SCANNING_NAME = s__('ciReport|Cluster Image Scanning'); export const SCANNER_NAMES_MAP = { @@ -273,15 +264,6 @@ export const securityFeatures = [ }, ]; -export const complianceFeatures = [ - { - name: LICENSE_COMPLIANCE_NAME, - description: LICENSE_COMPLIANCE_DESCRIPTION, - helpPath: LICENSE_COMPLIANCE_HELP_PATH, - type: REPORT_TYPE_LICENSE_COMPLIANCE, - }, -]; - export const featureToMutationMap = { [REPORT_TYPE_SAST]: { mutationId: 'configureSast', diff --git a/app/assets/javascripts/security_configuration/index.js b/app/assets/javascripts/security_configuration/index.js index 637d510e684..aa3c9c87622 100644 --- a/app/assets/javascripts/security_configuration/index.js +++ b/app/assets/javascripts/security_configuration/index.js @@ -3,7 +3,7 @@ import VueApollo from 'vue-apollo'; import createDefaultClient from '~/lib/graphql'; import { parseBooleanDataAttributes } from '~/lib/utils/dom_utils'; import SecurityConfigurationApp from './components/app.vue'; -import { securityFeatures, complianceFeatures } from './components/constants'; +import { securityFeatures } from './components/constants'; import { augmentFeatures } from './utils'; export const initSecurityConfiguration = (el) => { @@ -28,9 +28,8 @@ export const initSecurityConfiguration = (el) => { vulnerabilityTrainingDocsPath, } = el.dataset; - const { augmentedSecurityFeatures, augmentedComplianceFeatures } = augmentFeatures( + const { augmentedSecurityFeatures } = augmentFeatures( securityFeatures, - complianceFeatures, features ? JSON.parse(features) : [], ); @@ -48,7 +47,6 @@ export const initSecurityConfiguration = (el) => { render(createElement) { return createElement(SecurityConfigurationApp, { props: { - augmentedComplianceFeatures, augmentedSecurityFeatures, latestPipelinePath, gitlabCiHistoryPath, diff --git a/app/assets/javascripts/security_configuration/utils.js b/app/assets/javascripts/security_configuration/utils.js index df23698ba7e..72e6d870e13 100644 --- a/app/assets/javascripts/security_configuration/utils.js +++ b/app/assets/javascripts/security_configuration/utils.js @@ -2,19 +2,18 @@ import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import { SCANNER_NAMES_MAP } from '~/security_configuration/components/constants'; /** - * This function takes in 3 arrays of objects, securityFeatures, complianceFeatures and features. - * securityFeatures and complianceFeatures are static arrays living in the constants. + * This function takes in 3 arrays of objects, securityFeatures and features. + * securityFeatures are static arrays living in the constants. * features is dynamic and coming from the backend. * This function builds a superset of those arrays. * It looks for matching keys within the dynamic and the static arrays * and will enrich the objects with the available static data. * @param [{}] securityFeatures - * @param [{}] complianceFeatures * @param [{}] features * @returns {Object} Object with enriched features from constants divided into Security and Compliance Features */ -export const augmentFeatures = (securityFeatures, complianceFeatures, features = []) => { +export const augmentFeatures = (securityFeatures, features = []) => { const featuresByType = features.reduce((acc, feature) => { acc[feature.type] = convertObjectPropsToCamelCase(feature, { deep: true }); return acc; @@ -39,7 +38,6 @@ export const augmentFeatures = (securityFeatures, complianceFeatures, features = return { augmentedSecurityFeatures: securityFeatures.map((feature) => augmentFeature(feature)), - augmentedComplianceFeatures: complianceFeatures.map((feature) => augmentFeature(feature)), }; }; diff --git a/app/assets/javascripts/vue_shared/security_reports/constants.js b/app/assets/javascripts/vue_shared/security_reports/constants.js index 8b523645973..a1d75e08be9 100644 --- a/app/assets/javascripts/vue_shared/security_reports/constants.js +++ b/app/assets/javascripts/vue_shared/security_reports/constants.js @@ -27,7 +27,6 @@ export const REPORT_TYPE_CONTAINER_SCANNING = 'container_scanning'; export const REPORT_TYPE_CLUSTER_IMAGE_SCANNING = 'cluster_image_scanning'; export const REPORT_TYPE_COVERAGE_FUZZING = 'coverage_fuzzing'; export const REPORT_TYPE_CORPUS_MANAGEMENT = 'corpus_management'; -export const REPORT_TYPE_LICENSE_COMPLIANCE = 'license_scanning'; export const REPORT_TYPE_API_FUZZING = 'api_fuzzing'; export const REPORT_TYPE_MANUALLY_ADDED = 'generic'; diff --git a/app/assets/javascripts/work_items/components/work_item_detail.vue b/app/assets/javascripts/work_items/components/work_item_detail.vue index ddf7c789c09..ef99001c0e8 100644 --- a/app/assets/javascripts/work_items/components/work_item_detail.vue +++ b/app/assets/javascripts/work_items/components/work_item_detail.vue @@ -46,7 +46,8 @@ import workItemAssigneesSubscription from '../graphql/work_item_assignees.subscr import workItemMilestoneSubscription from '../graphql/work_item_milestone.subscription.graphql'; import updateWorkItemMutation from '../graphql/update_work_item.mutation.graphql'; import updateWorkItemTaskMutation from '../graphql/update_work_item_task.mutation.graphql'; -import { findHierarchyWidgetChildren, getWorkItemQuery } from '../utils'; +import workItemByIidQuery from '../graphql/work_item_by_iid.query.graphql'; +import { findHierarchyWidgetChildren } from '../utils'; import WorkItemTree from './work_item_links/work_item_tree.vue'; import WorkItemActions from './work_item_actions.vue'; @@ -137,18 +138,15 @@ export default { }, apollo: { workItem: { - query() { - return getWorkItemQuery(this.fetchByIid); - }, + query: workItemByIidQuery, variables() { return this.queryVariables; }, skip() { - return !this.workItemId && !this.workItemIid; + return !this.workItemIid; }, update(data) { - const workItem = this.fetchByIid ? data.workspace.workItems.nodes[0] : data.workItem; - return workItem ?? {}; + return data.workspace.workItems.nodes[0] ?? {}; }, error() { this.setEmptyState(); @@ -316,18 +314,11 @@ export default { workItemNotes() { return this.isWidgetPresent(WIDGET_TYPE_NOTES); }, - fetchByIid() { - return true; - }, queryVariables() { - return this.fetchByIid - ? { - fullPath: this.fullPath, - iid: this.workItemIid, - } - : { - id: this.workItemId, - }; + return { + fullPath: this.fullPath, + iid: this.workItemIid, + }; }, children() { return this.workItem ? findHierarchyWidgetChildren(this.workItem) : []; @@ -408,14 +399,12 @@ export default { }, toggleChildFromCache(workItem, childId, store) { const sourceData = store.readQuery({ - query: getWorkItemQuery(this.fetchByIid), + query: workItemByIidQuery, variables: this.queryVariables, }); const newData = produce(sourceData, (draftState) => { - const widgets = this.fetchByIid - ? draftState.workspace.workItems.nodes[0].widgets - : draftState.workItem.widgets; + const { widgets } = draftState.workspace.workItems.nodes[0]; const widgetHierarchy = widgets.find((widget) => widget.type === WIDGET_TYPE_HIERARCHY); const index = widgetHierarchy.children.nodes.findIndex((child) => child.id === childId); @@ -428,7 +417,7 @@ export default { }); store.writeQuery({ - query: getWorkItemQuery(this.fetchByIid), + query: workItemByIidQuery, variables: this.queryVariables, data: newData, }); @@ -475,12 +464,8 @@ export default { this.$emit('has-notes'); }, updateUrl(modalWorkItem) { - const params = this.fetchByIid - ? { work_item_iid: modalWorkItem?.iid } - : { work_item_id: getIdFromGraphQLId(modalWorkItem?.id) }; - updateHistory({ - url: setUrlParams(params), + url: setUrlParams({ work_item_iid: modalWorkItem?.iid }), replace: true, }); }, @@ -722,7 +707,6 @@ export default { :can-update="canUpdate" :project-path="fullPath" :confidential="workItem.confidential" - :fetch-by-iid="fetchByIid" @addWorkItemChild="addChild" @removeChild="removeChild" @show-modal="openInModal" diff --git a/app/assets/javascripts/work_items/components/work_item_links/work_item_tree.vue b/app/assets/javascripts/work_items/components/work_item_links/work_item_tree.vue index 3e5d9453fef..4dcc4d51957 100644 --- a/app/assets/javascripts/work_items/components/work_item_links/work_item_tree.vue +++ b/app/assets/javascripts/work_items/components/work_item_links/work_item_tree.vue @@ -61,11 +61,6 @@ export default { type: String, required: true, }, - fetchByIid: { - type: Boolean, - required: false, - default: false, - }, }, data() { return { @@ -174,7 +169,7 @@ export default { :work-item-id="workItemId" :work-item-iid="workItemIid" :work-item-type="workItemType" - :fetch-by-iid="fetchByIid" + fetch-by-iid @removeChild="$emit('removeChild', $event)" @show-modal="showModal" /> diff --git a/app/assets/stylesheets/page_bundles/tree.scss b/app/assets/stylesheets/page_bundles/tree.scss index 9d13ccc676d..a13b8704095 100644 --- a/app/assets/stylesheets/page_bundles/tree.scss +++ b/app/assets/stylesheets/page_bundles/tree.scss @@ -219,10 +219,3 @@ width: calc(100% + 24px); margin: -28px -12px 0; } - -.ai-genie-chat-message { - pre, - code { - @include gl-font-sm; - } -} diff --git a/app/assets/stylesheets/themes/dark_mode_overrides.scss b/app/assets/stylesheets/themes/dark_mode_overrides.scss index 619336686e8..3a18f735217 100644 --- a/app/assets/stylesheets/themes/dark_mode_overrides.scss +++ b/app/assets/stylesheets/themes/dark_mode_overrides.scss @@ -305,3 +305,8 @@ body.gl-dark { // lightens chat bubble in darkmode as $gray-50 matches drawer background. See tanuki_bot_chat.scss background-color: $gray-100; } + +.ai-genie-chat, +.ai-genie-chat .gl-form-input { + background-color: $gray-10; +} diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index d967aa89eb7..dbcbd2467b6 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -51,7 +51,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo push_frontend_feature_flag(:code_quality_inline_drawer, project) push_frontend_feature_flag(:hide_create_issue_resolve_all, project) push_frontend_feature_flag(:auto_merge_labels_mr_widget, project) - push_frontend_feature_flag(:summarize_my_code_review, current_user) + push_force_frontend_feature_flag(:summarize_my_code_review, summarize_my_code_review_enabled?) push_frontend_feature_flag(:mr_activity_filters, current_user) end @@ -603,6 +603,18 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo Date.strptime(date, "%Y-%m-%d")&.to_time&.to_i if date rescue Date::Error, TypeError end + + def summarize_my_code_review_enabled? + namespace = project&.group&.root_ancestor + return false if namespace.nil? + + Feature.enabled?(:summarize_my_code_review, current_user) && + namespace.group_namespace? && + namespace.licensed_feature_available?(:summarize_my_mr_code_review) && + namespace.experiment_features_enabled && + namespace.third_party_ai_features_enabled && + merge_request.send_to_ai? + end end Projects::MergeRequestsController.prepend_mod_with('Projects::MergeRequestsController') diff --git a/app/helpers/sessions_helper.rb b/app/helpers/sessions_helper.rb index 8251e1cba8a..9ef347fff16 100644 --- a/app/helpers/sessions_helper.rb +++ b/app/helpers/sessions_helper.rb @@ -48,4 +48,8 @@ module SessionsHelper # Moved to Gitlab::Utils::Email in 15.9 Gitlab::Utils::Email.obfuscated_email(email) end + + def remember_me_enabled? + Gitlab::CurrentSettings.remember_me_enabled? + end end diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index aba8727c030..8f57d73a6f4 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -38,10 +38,7 @@ class ApplicationSetting < MainClusterwide::ApplicationRecord add_authentication_token_field :static_objects_external_storage_auth_token, encrypted: :required add_authentication_token_field :error_tracking_access_token, encrypted: :required - belongs_to :self_monitoring_project, class_name: "Project", foreign_key: :instance_administration_project_id, - inverse_of: :application_setting belongs_to :push_rule - alias_attribute :self_monitoring_project_id, :instance_administration_project_id belongs_to :instance_group, class_name: "Group", foreign_key: :instance_administrators_group_id, inverse_of: :application_setting diff --git a/app/models/bulk_imports/configuration.rb b/app/models/bulk_imports/configuration.rb index 3b263ed0340..6d9f598583e 100644 --- a/app/models/bulk_imports/configuration.rb +++ b/app/models/bulk_imports/configuration.rb @@ -9,7 +9,7 @@ class BulkImports::Configuration < ApplicationRecord validates :url, :access_token, length: { maximum: 255 }, presence: true validates :url, public_url: { schemes: %w[http https], enforce_sanitization: true, ascii_only: true }, - allow_nil: true + allow_nil: true attr_encrypted :url, key: Settings.attr_encrypted_db_key_base_32, diff --git a/app/models/bulk_imports/entity.rb b/app/models/bulk_imports/entity.rb index b3540917197..94e4a8165eb 100644 --- a/app/models/bulk_imports/entity.rb +++ b/app/models/bulk_imports/entity.rb @@ -41,22 +41,14 @@ class BulkImports::Entity < ApplicationRecord validates :project, absence: true, if: :group validates :group, absence: true, if: :project validates :source_type, presence: true - validates :source_full_path, - presence: true, - format: { with: Gitlab::Regex.bulk_import_source_full_path_regex, - message: Gitlab::Regex.bulk_import_source_full_path_regex_message } + validates :source_full_path, presence: true, format: { + with: Gitlab::Regex.bulk_import_source_full_path_regex, + message: Gitlab::Regex.bulk_import_source_full_path_regex_message + } - validates :destination_name, - presence: true, - if: -> { group || project } - - validates :destination_namespace, - exclusion: [nil], - if: :group - - validates :destination_namespace, - presence: true, - if: :project? + validates :destination_name, presence: true, if: -> { group || project } + validates :destination_namespace, exclusion: [nil], if: :group + validates :destination_namespace, presence: true, if: :project? validate :validate_parent_is_a_group, if: :parent validate :validate_imported_entity_type @@ -72,9 +64,8 @@ class BulkImports::Entity < ApplicationRecord alias_attribute :destination_slug, :destination_name - delegate :default_project_visibility, - :default_group_visibility, - to: :'Gitlab::CurrentSettings.current_application_settings' + delegate :default_project_visibility, :default_group_visibility, + to: :'Gitlab::CurrentSettings.current_application_settings' state_machine :status, initial: :created do state :created, value: 0 diff --git a/app/models/clusters/kubernetes_namespace.rb b/app/models/clusters/kubernetes_namespace.rb index 42332bdc193..dfb5c4cc5eb 100644 --- a/app/models/clusters/kubernetes_namespace.rb +++ b/app/models/clusters/kubernetes_namespace.rb @@ -22,9 +22,9 @@ module Clusters delegate :api_url, to: :platform_kubernetes, allow_nil: true attr_encrypted :service_account_token, - mode: :per_attribute_iv, - key: Settings.attr_encrypted_db_key_base_truncated, - algorithm: 'aes-256-cbc' + mode: :per_attribute_iv, + key: Settings.attr_encrypted_db_key_base_truncated, + algorithm: 'aes-256-cbc' scope :has_service_account_token, -> { where.not(encrypted_service_account_token: nil) } scope :with_environment_name, -> (name) { joins(:environment).where(environments: { name: name }) } diff --git a/app/models/container_repository.rb b/app/models/container_repository.rb index 62b6effeb89..0f0abeae795 100644 --- a/app/models/container_repository.rb +++ b/app/models/container_repository.rb @@ -38,8 +38,8 @@ class ContainerRepository < ApplicationRecord validates :migration_aborted_in_state, inclusion: { in: ABORTABLE_MIGRATION_STATES }, allow_nil: true validates :migration_retries_count, presence: true, - numericality: { greater_than_or_equal_to: 0 }, - allow_nil: false + numericality: { greater_than_or_equal_to: 0 }, + allow_nil: false enum status: { delete_scheduled: 0, delete_failed: 1, delete_ongoing: 2 } enum expiration_policy_cleanup_status: { cleanup_unscheduled: 0, cleanup_scheduled: 1, cleanup_unfinished: 2, cleanup_ongoing: 3 } @@ -124,9 +124,7 @@ class ContainerRepository < ApplicationRecord state :import_done state :import_skipped do - validates :migration_skipped_reason, - :migration_skipped_at, - presence: true + validates :migration_skipped_reason, :migration_skipped_at, presence: true end state :import_aborted do @@ -603,8 +601,7 @@ class ContainerRepository < ApplicationRecord end def self.build_from_path(path) - self.new(project: path.repository_project, - name: path.repository_name) + self.new(project: path.repository_project, name: path.repository_name) end def self.find_or_create_from_path(path) @@ -622,13 +619,11 @@ class ContainerRepository < ApplicationRecord end def self.find_by_path!(path) - self.find_by!(project: path.repository_project, - name: path.repository_name) + self.find_by!(project: path.repository_project, name: path.repository_name) end def self.find_by_path(path) - self.find_by(project: path.repository_project, - name: path.repository_name) + self.find_by(project: path.repository_project, name: path.repository_name) end private diff --git a/app/models/cycle_analytics/project_level_stage_adapter.rb b/app/models/cycle_analytics/project_level_stage_adapter.rb index 9b9c0822f63..ae21a4a6bfe 100644 --- a/app/models/cycle_analytics/project_level_stage_adapter.rb +++ b/app/models/cycle_analytics/project_level_stage_adapter.rb @@ -16,12 +16,12 @@ module CycleAnalytics presenter = Analytics::CycleAnalytics::StagePresenter.new(stage) serializer.new.represent(ProjectLevelStage.new( - title: presenter.title, - description: presenter.description, - legend: presenter.legend, - name: stage.name, - project_median: median - )) + title: presenter.title, + description: presenter.description, + legend: presenter.legend, + name: stage.name, + project_median: median + )) end # rubocop: enable CodeReuse/Presenter diff --git a/app/models/deployment.rb b/app/models/deployment.rb index f8873d388a3..f3ee21ea4e0 100644 --- a/app/models/deployment.rb +++ b/app/models/deployment.rb @@ -372,9 +372,11 @@ class Deployment < ApplicationRecord # i.e.: # MergeRequest.select(1, 2).to_sql #=> SELECT 1, 2 FROM "merge_requests" # MergeRequest.select(1, 1).to_sql #=> SELECT 1 FROM "merge_requests" - select = relation.select('merge_requests.id', - "#{id} as deployment_id", - "#{environment_id} as environment_id").to_sql + select = relation.select( + 'merge_requests.id', + "#{id} as deployment_id", + "#{environment_id} as environment_id" + ).to_sql # We don't use `ApplicationRecord.legacy_bulk_insert` here so that we don't need to # first pluck lots of IDs into memory. diff --git a/app/models/design_management/design.rb b/app/models/design_management/design.rb index cb6d4e72c80..505935bb230 100644 --- a/app/models/design_management/design.rb +++ b/app/models/design_management/design.rb @@ -31,8 +31,8 @@ module DesignManagement has_many :events, as: :target, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent has_internal_id :iid, scope: :project, presence: true, - hook_names: %i[create update], # Deal with old records - track_if: -> { !importing? } + hook_names: %i[create update], # Deal with old records + track_if: -> { !importing? } validates :project, :filename, presence: true validates :issue, presence: true, unless: :importing? diff --git a/app/models/design_management/version.rb b/app/models/design_management/version.rb index 5819404efb9..dd6812f0eac 100644 --- a/app/models/design_management/version.rb +++ b/app/models/design_management/version.rb @@ -36,10 +36,10 @@ module DesignManagement belongs_to :author, class_name: 'User' has_many :actions has_many :designs, - through: :actions, - class_name: "DesignManagement::Design", - source: :design, - inverse_of: :versions + through: :actions, + class_name: "DesignManagement::Design", + source: :design, + inverse_of: :versions validates :designs, presence: true, unless: :importing? validates :sha, presence: true diff --git a/app/models/diff_discussion.rb b/app/models/diff_discussion.rb index 041ec98ffc9..e2ee951522d 100644 --- a/app/models/diff_discussion.rb +++ b/app/models/diff_discussion.rb @@ -10,13 +10,13 @@ class DiffDiscussion < Discussion DiffNote end - delegate :position, - :original_position, - :change_position, - :diff_note_positions, - :on_text?, - :on_image?, - to: :first_note + delegate :position, + :original_position, + :change_position, + :diff_note_positions, + :on_text?, + :on_image?, + to: :first_note def legacy_diff_discussion? false diff --git a/app/models/diff_viewer/base.rb b/app/models/diff_viewer/base.rb index 75aa51348c8..05552e83700 100644 --- a/app/models/diff_viewer/base.rb +++ b/app/models/diff_viewer/base.rb @@ -101,8 +101,9 @@ module DiffViewer def render_error_options options = [] - blob_url = Gitlab::Routing.url_helpers.project_blob_path(diff_file.repository.project, - File.join(diff_file.content_sha, diff_file.file_path)) + blob_url = Gitlab::Routing.url_helpers.project_blob_path( + diff_file.repository.project, File.join(diff_file.content_sha, diff_file.file_path) + ) options << ActionController::Base.helpers.link_to(_('view the blob'), blob_url) options diff --git a/app/models/integrations/prometheus.rb b/app/models/integrations/prometheus.rb index 2f0995e9ab0..b148539dec6 100644 --- a/app/models/integrations/prometheus.rb +++ b/app/models/integrations/prometheus.rb @@ -99,8 +99,7 @@ module Integrations end def allow_local_api_url? - allow_local_requests_from_web_hooks_and_services? || - (self_monitoring_project? && internal_prometheus_url?) + allow_local_requests_from_web_hooks_and_services? || internal_prometheus_url? end def configured? @@ -127,10 +126,6 @@ module Integrations delegate :allow_local_requests_from_web_hooks_and_services?, to: :current_settings, private: true - def self_monitoring_project? - project && project.id == current_settings.self_monitoring_project_id - end - def internal_prometheus_url? api_url.present? && api_url == ::Gitlab::Prometheus::Internal.uri end diff --git a/app/models/organization.rb b/app/models/organization.rb new file mode 100644 index 00000000000..73a7e84305f --- /dev/null +++ b/app/models/organization.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +# rubocop: disable Gitlab/NamespacedClass +class Organization < ApplicationRecord +end +# rubocop: enable Gitlab/NamespacedClass diff --git a/app/models/project.rb b/app/models/project.rb index 8f9b7042a0e..16719316ede 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -173,7 +173,7 @@ class Project < ApplicationRecord has_one :last_event, -> { order 'events.created_at DESC' }, class_name: 'Event' has_many :boards - has_many :application_setting, inverse_of: :self_monitoring_project + has_many :application_setting def self.integration_association_name(name) "#{name}_integration" @@ -2857,10 +2857,6 @@ class Project < ApplicationRecord Feature.enabled?(:group_protected_branches, group) || Feature.enabled?(:allow_protected_branches_for_group, group) end - def self_monitoring? - Gitlab::CurrentSettings.self_monitoring_project_id == id - end - def deploy_token_create_url(opts = {}) Gitlab::Routing.url_helpers.create_deploy_token_project_settings_repository_path(self, opts) end diff --git a/app/models/user.rb b/app/models/user.rb index a5d8541c8ef..f0797e41c12 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1077,6 +1077,13 @@ class User < ApplicationRecord update(otp_backup_codes: nil) end + # Returns true if the user is allowed to sign in with either otp or recovery codes. + def sign_in_with_codes_allowed? + return two_factor_otp_enabled? unless Feature.enabled?(:webauthn_without_totp) + + two_factor_enabled? + end + def two_factor_enabled? two_factor_otp_enabled? || two_factor_webauthn_enabled? end diff --git a/app/services/members/destroy_service.rb b/app/services/members/destroy_service.rb index dd84b890385..0f195663a61 100644 --- a/app/services/members/destroy_service.rb +++ b/app/services/members/destroy_service.rb @@ -15,18 +15,39 @@ module Members @skip_auth = skip_authorization if a_group_owner?(member) - process_destroy_of_group_owner_member(member, skip_subresources, unassign_issuables) + process_destroy_of_group_owner_member(member, skip_subresources) else destroy_member(member) - destroy_data_related_to_member(member, skip_subresources, unassign_issuables) + destroy_data_related_to_member(member, skip_subresources) end + enqueue_jobs_that_needs_to_be_run_only_once_per_hierarchy(member, unassign_issuables) + member end + # We use this to mark recursive calls made to this service from within the same service. + # We do this so as to help us run some tasks that needs to be run only once per hierarchy, and not recursively. + def mark_as_recursive_call + @recursive_call = true + end + private - def process_destroy_of_group_owner_member(member, skip_subresources, unassign_issuables) + # These actions need to be executed only once per hierarchy because the underlying services + # apply these actions to the entire hierarchy anyway, so there is no need to execute them recursively. + def enqueue_jobs_that_needs_to_be_run_only_once_per_hierarchy(member, unassign_issuables) + return if recursive_call? + + enqueue_delete_todos(member) + enqueue_unassign_issuables(member) if unassign_issuables + end + + def recursive_call? + @recursive_call == true + end + + def process_destroy_of_group_owner_member(member, skip_subresources) # Deleting 2 different group owners via the API in quick succession could lead to # wrong results for the `last_owner?` check due to race conditions. To prevent this # we wrap both the last_owner? check and the deletes of owners within a lock. @@ -40,23 +61,23 @@ module Members end # deletion of related data does not have to be within the lock. - destroy_data_related_to_member(member, skip_subresources, unassign_issuables) unless last_group_owner + destroy_data_related_to_member(member, skip_subresources) unless last_group_owner end def destroy_member(member) member.destroy end - def destroy_data_related_to_member(member, skip_subresources, unassign_issuables) + def destroy_data_related_to_member(member, skip_subresources) member.user&.invalidate_cache_counts - delete_member_associations(member, skip_subresources, unassign_issuables) + delete_member_associations(member, skip_subresources) end def a_group_owner?(member) member.is_a?(GroupMember) && member.owner? end - def delete_member_associations(member, skip_subresources, unassign_issuables) + def delete_member_associations(member, skip_subresources) if member.request? && member.user != current_user notification_service.decline_access_request(member) end @@ -64,8 +85,6 @@ module Members delete_subresources(member) unless skip_subresources delete_project_invitations_by(member) unless skip_subresources resolve_access_request_todos(current_user, member) - enqueue_delete_todos(member) - enqueue_unassign_issuables(member) if unassign_issuables after_execute(member: member) end @@ -110,13 +129,17 @@ module Members def destroy_project_members(members) members.each do |project_member| - self.class.new(current_user).execute(project_member, skip_authorization: @skip_auth) + service = self.class.new(current_user) + service.mark_as_recursive_call + service.execute(project_member, skip_authorization: @skip_auth) end end def destroy_group_members(members) members.each do |group_member| - self.class.new(current_user).execute(group_member, skip_authorization: @skip_auth, skip_subresources: true) + service = self.class.new(current_user) + service.mark_as_recursive_call + service.execute(group_member, skip_authorization: @skip_auth, skip_subresources: true) end end diff --git a/app/views/admin/sessions/_signin_box.html.haml b/app/views/admin/sessions/_signin_box.html.haml index c7382266480..2cd2eabd4b7 100644 --- a/app/views/admin/sessions/_signin_box.html.haml +++ b/app/views/admin/sessions/_signin_box.html.haml @@ -7,7 +7,7 @@ - ldap_servers.each_with_index do |server, i| .login-box.tab-pane{ id: "#{server['provider_name']}", role: 'tabpanel', class: active_when(i == 0 && form_based_auth_provider_has_active_class?(:ldapmain)) } .login-body - = render 'devise/sessions/new_ldap', server: server, hide_remember_me: true, submit_message: _('Enter Admin Mode') + = render 'devise/sessions/new_ldap', server: server, render_remember_me: false, submit_message: _('Enter Admin Mode') = render_if_exists 'devise/sessions/new_smartcard' diff --git a/app/views/admin/sessions/new.html.haml b/app/views/admin/sessions/new.html.haml index e9442cf6b53..21b2c6df014 100644 --- a/app/views/admin/sessions/new.html.haml +++ b/app/views/admin/sessions/new.html.haml @@ -20,4 +20,4 @@ - if omniauth_enabled? && button_based_providers_enabled? .clearfix - = render 'devise/shared/omniauth_box', hide_remember_me: true + = render 'devise/shared/omniauth_box', render_remember_me: false diff --git a/app/views/admin/sessions/two_factor.html.haml b/app/views/admin/sessions/two_factor.html.haml index d02090d4880..33d73745138 100644 --- a/app/views/admin/sessions/two_factor.html.haml +++ b/app/views/admin/sessions/two_factor.html.haml @@ -9,7 +9,7 @@ .tab-content .login-box.tab-pane.gl-p-5.active{ id: 'login-pane', role: 'tabpanel' } .login-body - - if current_user.two_factor_otp_enabled? + - if current_user.sign_in_with_codes_allowed? = render 'admin/sessions/two_factor_otp' - if current_user.two_factor_webauthn_enabled? = render 'authentication/authenticate', render_remember_me: false, target_path: admin_session_path diff --git a/app/views/authentication/_authenticate.html.haml b/app/views/authentication/_authenticate.html.haml index 7d7bd395836..9e09bcd6e54 100644 --- a/app/views/authentication/_authenticate.html.haml +++ b/app/views/authentication/_authenticate.html.haml @@ -21,7 +21,7 @@ %div %p= _("We heard back from your device. You have been authenticated.") = form_tag(target_path, method: :post, id: 'js-login-token-2fa-form') do |f| - - if render_remember_me + - if remember_me_enabled? && render_remember_me - resource_params = params[resource_name].presence || params = hidden_field_tag 'user[remember_me]', resource_params.fetch(:remember_me, 0) = hidden_field_tag 'user[device_response]', nil, class: 'form-control', required: true, id: "js-device-response" diff --git a/app/views/devise/sessions/_new_base.html.haml b/app/views/devise/sessions/_new_base.html.haml index 3aeb89979bb..1c40931c5bb 100644 --- a/app/views/devise/sessions/_new_base.html.haml +++ b/app/views/devise/sessions/_new_base.html.haml @@ -5,15 +5,15 @@ .form-group.gl-px-5 = f.label :password, class: "label-bold #{'gl-mb-1' if Feature.enabled?(:restyle_login_page, @project)}" = f.password_field :password, class: 'form-control gl-form-input bottom', autocomplete: 'current-password', required: true, title: _('This field is required.'), data: { qa_selector: 'password_field', testid: 'password-field' } - - if devise_mapping.rememberable? - .gl-px-5 - .gl-display-inline-block + .gl-px-5 + .gl-display-inline-block + - if remember_me_enabled? = f.gitlab_ui_checkbox_component :remember_me, _('Remember me') - .gl-float-right - - if unconfirmed_email? - = link_to _('Resend confirmation email'), new_user_confirmation_path - - else - = link_to _('Forgot your password?'), new_password_path(:user) + .gl-float-right + - if unconfirmed_email? + = link_to _('Resend confirmation email'), new_user_confirmation_path + - else + = link_to _('Forgot your password?'), new_password_path(:user) %div - if Feature.enabled?(:arkose_labs_login_challenge) = render_if_exists 'devise/sessions/arkose_labs' diff --git a/app/views/devise/sessions/_new_crowd.html.haml b/app/views/devise/sessions/_new_crowd.html.haml index bdf357c5f74..14038f3c3c7 100644 --- a/app/views/devise/sessions/_new_crowd.html.haml +++ b/app/views/devise/sessions/_new_crowd.html.haml @@ -5,7 +5,7 @@ .form-group.gl-px-5 = label_tag :password = password_field_tag :password, nil, { autocomplete: 'current-password', class: "form-control bottom", title: _("This field is required."), required: true } - - if devise_mapping.rememberable? + - if remember_me_enabled? .remember-me.gl-px-5 %label{ for: "remember_me" } = check_box_tag :remember_me, '1', false, id: 'remember_me' diff --git a/app/views/devise/sessions/_new_ldap.html.haml b/app/views/devise/sessions/_new_ldap.html.haml index 7affbafbdeb..8f9d6ee556e 100644 --- a/app/views/devise/sessions/_new_ldap.html.haml +++ b/app/views/devise/sessions/_new_ldap.html.haml @@ -1,5 +1,5 @@ - server = local_assigns.fetch(:server) -- hide_remember_me = local_assigns.fetch(:hide_remember_me, false) +- render_remember_me = remember_me_enabled? && local_assigns.fetch(:render_remember_me, true) - submit_message = local_assigns.fetch(:submit_message, _('Sign in')) = form_tag(omniauth_callback_path(:user, server['provider_name']), id: 'new_ldap_user', class: "gl-show-field-errors") do @@ -9,7 +9,7 @@ .form-group.gl-px-5 = label_tag :password = password_field_tag :password, nil, { autocomplete: 'current-password', class: "form-control gl-form-input bottom", title: _("This field is required."), data: { qa_selector: 'password_field' }, required: true } - - if !hide_remember_me && devise_mapping.rememberable? + - if render_remember_me .gl-px-5 = render Pajamas::CheckboxTagComponent.new(name: 'remember_me') do |c| = c.label do diff --git a/app/views/devise/sessions/two_factor.html.haml b/app/views/devise/sessions/two_factor.html.haml index 12e5a7263f7..789f6072b68 100644 --- a/app/views/devise/sessions/two_factor.html.haml +++ b/app/views/devise/sessions/two_factor.html.haml @@ -2,10 +2,11 @@ = render 'devise/shared/tab_single', tab_title: _('Two-Factor Authentication') if Feature.disabled?(:restyle_login_page, @project) .login-box.gl-p-5 .login-body - - if @user.two_factor_otp_enabled? || (Feature.enabled?(:webauthn_without_totp) && @user.two_factor_enabled?) + - if @user.sign_in_with_codes_allowed? = gitlab_ui_form_for(resource, as: resource_name, url: session_path(resource_name), method: :post, html: { class: "edit_user gl-show-field-errors js-2fa-form #{'hidden' if @user.two_factor_webauthn_enabled?}" }) do |f| - resource_params = params[resource_name].presence || params - = f.hidden_field :remember_me, value: resource_params.fetch(:remember_me, 0) + - if remember_me_enabled? + = f.hidden_field :remember_me, value: resource_params.fetch(:remember_me, 0) %div = f.label _('Enter verification code'), name: :otp_attempt, class: Feature.enabled?(:restyle_login_page, @project) ? 'gl-mb-1' : '' = f.text_field :otp_attempt, class: 'form-control gl-form-input', required: true, autofocus: true, autocomplete: 'off', inputmode: 'numeric', title: _('This field is required.'), data: { qa_selector: 'two_fa_code_field' } diff --git a/app/views/devise/shared/_omniauth_box.html.haml b/app/views/devise/shared/_omniauth_box.html.haml index 150f61a97e0..f59bdc67d17 100644 --- a/app/views/devise/shared/_omniauth_box.html.haml +++ b/app/views/devise/shared/_omniauth_box.html.haml @@ -1,4 +1,4 @@ -- hide_remember_me = local_assigns.fetch(:hide_remember_me, false) +- render_remember_me = remember_me_enabled? && local_assigns.fetch(:render_remember_me, true) - restyle_login_page_enabled = Feature.enabled?(:restyle_login_page, @project) %div{ class: restyle_login_page_enabled ? 'omniauth-container gl-mt-5 gl-p-5 gl-text-center gl-w-90p gl-ml-auto gl-mr-auto' : 'omniauth-container gl-mt-5 gl-p-5' } %label{ class: restyle_login_page_enabled ? 'gl-font-weight-normal' : 'gl-font-weight-bold' } @@ -12,7 +12,7 @@ = provider_image_tag(provider) %span.gl-button-text = label_for_provider(provider) - - unless hide_remember_me + - if render_remember_me = render Pajamas::CheckboxTagComponent.new(name: 'remember_me_omniauth', value: nil) do |c| = c.label do = _('Remember me') diff --git a/app/views/projects/_self_monitoring_deprecation_notice.html.haml b/app/views/projects/_self_monitoring_deprecation_notice.html.haml deleted file mode 100644 index b9e32356688..00000000000 --- a/app/views/projects/_self_monitoring_deprecation_notice.html.haml +++ /dev/null @@ -1,13 +0,0 @@ -- return unless project.self_monitoring? - -= content_for :page_level_alert do - .flash-container.flash-container-page.sticky - %div{ class: [container_class, 'limit-container-width', 'gl-pt-5!'] } - = render Pajamas::AlertComponent.new(title: _('Deprecation notice'), - variant: :danger, - alert_options: { class: 'gl-mb-3 gl-sticky' }) do |c| - = c.body do - - deprecation_link = '<a href="%{url}">'.html_safe % { url: help_page_path('update/deprecations', anchor: 'gitlab-self-monitoring-project') } - - removal_link = '<a href="%{url}">'.html_safe % { url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/348909' } - - opstrace_link = '<a href="%{url}">'.html_safe % { url: 'https://gitlab.com/groups/gitlab-org/-/epics/6976' } - = _("Self-monitoring was %{deprecation}deprecated%{link_end} in GitLab 14.9, and is %{removal}scheduled for removal%{link_end} in GitLab 16.0. For information on a possible replacement, %{opstrace}learn more about Opstrace%{link_end}.").html_safe % { deprecation: deprecation_link, removal: removal_link, opstrace: opstrace_link, link_end: '</a>'.html_safe } diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml index b6c21588193..a51d1080d96 100644 --- a/app/views/projects/empty.html.haml +++ b/app/views/projects/empty.html.haml @@ -7,7 +7,6 @@ = render "home_panel" = render "archived_notice", project: @project -= render "self_monitoring_deprecation_notice", project: @project = render "invite_members_empty_project" if can_admin_project_member?(@project) diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index ab2f6745dfd..e21bf7d318b 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -8,7 +8,6 @@ = render_if_exists 'shared/ultimate_feature_removal_banner', project: @project = render partial: 'flash_messages', locals: { project: @project } -= render "self_monitoring_deprecation_notice", project: @project = render 'clusters_deprecation_alert' |