Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
50b6f6a788
commit
08608c8e9e
|
|
@ -359,6 +359,7 @@ oauth:
|
|||
variables:
|
||||
QA_SCENARIO: Test::Integration::OAuth
|
||||
rules:
|
||||
- when: manual
|
||||
- !reference [.rules:test:qa-default-branch, rules]
|
||||
- if: $QA_SUITES =~ /Test::Integration::OAuth/
|
||||
- !reference [.rules:test:manual, rules]
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
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,
|
||||
|
|
@ -211,7 +210,6 @@ export const securityFeatures = [
|
|||
configurationHelpPath: DEPENDENCY_SCANNING_CONFIG_HELP_PATH,
|
||||
type: REPORT_TYPE_DEPENDENCY_SCANNING,
|
||||
anchor: 'dependency-scanning',
|
||||
slotComponent: ContinuousVulnerabilityScan,
|
||||
},
|
||||
{
|
||||
name: CONTAINER_SCANNING_NAME,
|
||||
|
|
|
|||
|
|
@ -1,133 +0,0 @@
|
|||
<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;
|
||||
},
|
||||
hasCvsSection() {
|
||||
return (
|
||||
this.glFeatures.dependencyScanningOnAdvisoryIngestion &&
|
||||
!this.glFeatures.globalDependencyScanningOnAdvisoryIngestion
|
||||
);
|
||||
},
|
||||
},
|
||||
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="hasCvsSection">
|
||||
<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>
|
||||
|
|
@ -73,9 +73,6 @@ 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
|
||||
|
|
@ -221,9 +218,5 @@ export default {
|
|||
{{ $options.i18n.configurationGuide }}
|
||||
</gl-button>
|
||||
</div>
|
||||
|
||||
<div v-if="hasSlotComponent">
|
||||
<component :is="feature.slotComponent" :feature="feature" />
|
||||
</div>
|
||||
</gl-card>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@ export const initSecurityConfiguration = (el) => {
|
|||
autoDevopsHelpPagePath,
|
||||
autoDevopsPath,
|
||||
vulnerabilityTrainingDocsPath,
|
||||
continuousVulnerabilityScansEnabled,
|
||||
} = el.dataset;
|
||||
|
||||
const { augmentedSecurityFeatures } = augmentFeatures(
|
||||
|
|
@ -44,7 +43,6 @@ export const initSecurityConfiguration = (el) => {
|
|||
autoDevopsHelpPagePath,
|
||||
autoDevopsPath,
|
||||
vulnerabilityTrainingDocsPath,
|
||||
continuousVulnerabilityScansEnabled,
|
||||
},
|
||||
render(createElement) {
|
||||
return createElement(SecurityConfigurationApp, {
|
||||
|
|
|
|||
|
|
@ -166,6 +166,8 @@ class Projects::PipelinesController < Projects::ApplicationController
|
|||
@stage = pipeline.stage(params[:stage])
|
||||
return not_found unless @stage
|
||||
|
||||
return unless stage_stale?
|
||||
|
||||
render json: StageSerializer
|
||||
.new(project: @project, current_user: @current_user)
|
||||
.represent(@stage, details: true, retried: params[:retried])
|
||||
|
|
@ -263,6 +265,15 @@ class Projects::PipelinesController < Projects::ApplicationController
|
|||
redirect_to url_for(safe_params.except(:scope).merge(status: safe_params[:scope])), status: :moved_permanently
|
||||
end
|
||||
|
||||
def stage_stale?
|
||||
return true if Feature.disabled?(:pipeline_stage_set_last_modified, @current_user)
|
||||
|
||||
last_modified = [@stage.updated_at.utc, @stage.statuses.maximum(:updated_at)].max
|
||||
|
||||
expires_in 24.hours
|
||||
stale?(last_modified: last_modified, etag: @stage)
|
||||
end
|
||||
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
def pipeline
|
||||
return @pipeline if defined?(@pipeline)
|
||||
|
|
|
|||
|
|
@ -118,6 +118,10 @@ module ProjectFeaturesCompatibility
|
|||
write_feature_attribute_string(:model_experiments_access_level, value)
|
||||
end
|
||||
|
||||
def model_registry_access_level=(value)
|
||||
write_feature_attribute_string(:model_registry_access_level, value)
|
||||
end
|
||||
|
||||
# TODO: Remove this method after we drop support for project create/edit APIs to set the
|
||||
# container_registry_enabled attribute. They can instead set the container_registry_access_level
|
||||
# attribute.
|
||||
|
|
|
|||
|
|
@ -94,16 +94,31 @@ module Routable
|
|||
"(LOWER(routes.path) = LOWER(#{connection.quote(path)}))"
|
||||
end
|
||||
|
||||
route =
|
||||
if use_includes
|
||||
includes(:route).references(:routes)
|
||||
else
|
||||
joins(:route)
|
||||
end
|
||||
if Feature.enabled?(:optimize_where_full_path_in, Feature.current_request)
|
||||
route_scope = all
|
||||
source_type_condition = { source_type: route_scope.klass.base_class }
|
||||
|
||||
route
|
||||
.where(wheres.join(' OR '))
|
||||
.allow_cross_joins_across_databases(url: "https://gitlab.com/gitlab-org/gitlab/-/issues/420046")
|
||||
routes_matching_condition = Route.where(source_type_condition).where(wheres.join(' OR '))
|
||||
|
||||
result = route_scope.where(id: routes_matching_condition.pluck(:source_id))
|
||||
|
||||
if use_includes
|
||||
result.preload(:route)
|
||||
else
|
||||
result
|
||||
end
|
||||
else
|
||||
route =
|
||||
if use_includes
|
||||
includes(:route).references(:routes)
|
||||
else
|
||||
joins(:route)
|
||||
end
|
||||
|
||||
route
|
||||
.where(wheres.join(' OR '))
|
||||
.allow_cross_joins_across_databases(url: "https://gitlab.com/gitlab-org/gitlab/-/issues/420046")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -499,7 +499,7 @@ class Project < ApplicationRecord
|
|||
accepts_nested_attributes_for :prometheus_integration, update_only: true
|
||||
accepts_nested_attributes_for :alerting_setting, update_only: true
|
||||
|
||||
delegate :merge_requests_access_level, :forking_access_level, :issues_access_level, :wiki_access_level, :snippets_access_level, :builds_access_level, :repository_access_level, :package_registry_access_level, :pages_access_level, :metrics_dashboard_access_level, :analytics_access_level, :operations_access_level, :security_and_compliance_access_level, :container_registry_access_level, :environments_access_level, :feature_flags_access_level, :monitor_access_level, :releases_access_level, :infrastructure_access_level, :model_experiments_access_level, to: :project_feature, allow_nil: true
|
||||
delegate :merge_requests_access_level, :forking_access_level, :issues_access_level, :wiki_access_level, :snippets_access_level, :builds_access_level, :repository_access_level, :package_registry_access_level, :pages_access_level, :metrics_dashboard_access_level, :analytics_access_level, :operations_access_level, :security_and_compliance_access_level, :container_registry_access_level, :environments_access_level, :feature_flags_access_level, :monitor_access_level, :releases_access_level, :infrastructure_access_level, :model_experiments_access_level, :model_registry_access_level, to: :project_feature, allow_nil: true
|
||||
delegate :name, to: :owner, allow_nil: true, prefix: true
|
||||
delegate :jira_dvcs_server_last_sync_at, to: :feature_usage
|
||||
delegate :last_pipeline, to: :commit, allow_nil: true
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ class ProjectFeature < ApplicationRecord
|
|||
releases
|
||||
infrastructure
|
||||
model_experiments
|
||||
model_registry
|
||||
].freeze
|
||||
|
||||
EXPORTABLE_FEATURES = (FEATURES - [:security_and_compliance, :pages]).freeze
|
||||
|
|
@ -81,6 +82,7 @@ class ProjectFeature < ApplicationRecord
|
|||
attribute :feature_flags_access_level, default: ENABLED
|
||||
attribute :environments_access_level, default: ENABLED
|
||||
attribute :model_experiments_access_level, default: ENABLED
|
||||
attribute :model_registry_access_level, default: ENABLED
|
||||
|
||||
attribute :package_registry_access_level, default: -> do
|
||||
if ::Gitlab.config.packages.enabled
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@
|
|||
= f.text_field :mastodon, class: 'gl-form-input form-control gl-md-form-input-lg', placeholder: "@robin@example.com"
|
||||
|
||||
.form-group.gl-form-group
|
||||
= f.label :website_url, s_('Profiles|Website url')
|
||||
= f.label :website_url, s_('Profiles|Website URL')
|
||||
= f.text_field :website_url, class: 'gl-form-input form-control gl-md-form-input-lg', placeholder: s_("Profiles|https://website.com")
|
||||
.form-group.gl-form-group
|
||||
= f.label :location, s_('Profiles|Location')
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
name: project_feature_model_registry_access_level_updated
|
||||
description: Model registry access level was updated
|
||||
introduced_by_issue: https://gitlab.com/gitlab-org/gitlab/-/issues/412734
|
||||
introduced_by_mr: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/138399
|
||||
feature_category: mlops
|
||||
milestone: '16.7'
|
||||
saved_to_database: true
|
||||
streamed: true
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
name: global_dependency_scanning_on_advisory_ingestion
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/135581
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/427424
|
||||
milestone: '16.6'
|
||||
type: development
|
||||
group: group::composition analysis
|
||||
default_enabled: true
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: optimize_where_full_path_in
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/137886
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/432863
|
||||
milestone: '16.7'
|
||||
type: development
|
||||
group: group::tenant scale
|
||||
default_enabled: false
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: pipeline_stage_set_last_modified
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/138499
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/433359
|
||||
milestone: '16.7'
|
||||
type: development
|
||||
group: group::global search
|
||||
default_enabled: false
|
||||
|
|
@ -511,8 +511,6 @@
|
|||
- 1
|
||||
- - package_cleanup
|
||||
- 1
|
||||
- - package_metadata_advisory_scan
|
||||
- 1
|
||||
- - package_metadata_global_advisory_scan
|
||||
- 1
|
||||
- - package_repositories
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ModifyCiFinishedBuildsStartedAtDefault < ClickHouse::Migration
|
||||
def up
|
||||
execute <<~SQL
|
||||
ALTER TABLE ci_finished_builds MODIFY COLUMN started_at DEFAULT COALESCE(finished_at, 0)
|
||||
SQL
|
||||
end
|
||||
|
||||
def down
|
||||
execute <<~SQL
|
||||
ALTER TABLE ci_finished_builds MODIFY COLUMN started_at DEFAULT now()
|
||||
SQL
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ModifyCiFinishedBuildsFinishedAtDefault < ClickHouse::Migration
|
||||
def up
|
||||
execute <<~SQL
|
||||
ALTER TABLE ci_finished_builds MODIFY COLUMN finished_at DEFAULT 0
|
||||
SQL
|
||||
end
|
||||
|
||||
def down
|
||||
execute <<~SQL
|
||||
ALTER TABLE ci_finished_builds MODIFY COLUMN finished_at DEFAULT now()
|
||||
SQL
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class FixInvalidCiFinishedBuildsStartedAtValues < ClickHouse::Migration
|
||||
def up
|
||||
# Fix existing records to have the new default
|
||||
execute <<~SQL
|
||||
ALTER TABLE ci_finished_builds UPDATE started_at = finished_at WHERE started_at > finished_at
|
||||
SQL
|
||||
end
|
||||
|
||||
def down
|
||||
# no-op as there is no way to retrieve old data
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddModelRegistryAccessLevelToProjectFeature < Gitlab::Database::Migration[2.2]
|
||||
OPERATIONS_DEFAULT_VALUE = 20
|
||||
|
||||
enable_lock_retries!
|
||||
milestone '16.7'
|
||||
|
||||
def change
|
||||
add_column :project_features,
|
||||
:model_registry_access_level,
|
||||
:integer,
|
||||
null: false,
|
||||
default: OPERATIONS_DEFAULT_VALUE
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
5c9d89f5d5401d6a7082d5790cb12ee610a0a06138cf3608534a09685c812ea8
|
||||
|
|
@ -21881,7 +21881,8 @@ CREATE TABLE project_features (
|
|||
feature_flags_access_level integer DEFAULT 20 NOT NULL,
|
||||
environments_access_level integer DEFAULT 20 NOT NULL,
|
||||
releases_access_level integer DEFAULT 20 NOT NULL,
|
||||
model_experiments_access_level integer DEFAULT 20 NOT NULL
|
||||
model_experiments_access_level integer DEFAULT 20 NOT NULL,
|
||||
model_registry_access_level integer DEFAULT 20 NOT NULL
|
||||
);
|
||||
|
||||
CREATE SEQUENCE project_features_id_seq
|
||||
|
|
|
|||
|
|
@ -313,6 +313,7 @@ Audit event types belong to the following product categories.
|
|||
| Name | Description | Saved to database | Streamed | Introduced in |
|
||||
|:-----|:------------|:------------------|:---------|:--------------|
|
||||
| [`project_feature_model_experiments_access_level_updated`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/121027) | Model experiments access level was updated| **{check-circle}** Yes | **{check-circle}** Yes | GitLab [16.1](https://gitlab.com/gitlab-org/gitlab/-/issues/412384) |
|
||||
| [`project_feature_model_registry_access_level_updated`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/138399) | Model registry access level was updated| **{check-circle}** Yes | **{check-circle}** Yes | GitLab [16.7](https://gitlab.com/gitlab-org/gitlab/-/issues/412734) |
|
||||
|
||||
### Not categorized
|
||||
|
||||
|
|
|
|||
|
|
@ -218,8 +218,6 @@ Use sentence case for topic titles. For example:
|
|||
|
||||
When referring to specific user interface text, like a button label or menu
|
||||
item, use the same capitalization that's displayed in the user interface.
|
||||
Standards for this content are listed in the [Pajamas Design System Content section](https://design.gitlab.com/content/punctuation/)
|
||||
and typically match what's mentioned in this Documentation Style Guide.
|
||||
|
||||
If you think the user interface text contains style mistakes,
|
||||
create an issue or an MR to propose a change to the user interface text.
|
||||
|
|
|
|||
|
|
@ -406,6 +406,7 @@ The following table lists group permissions available for each role:
|
|||
| Manage group runners | | | | | ✓ |
|
||||
| [Migrate groups](group/import/index.md) | | | | | ✓ |
|
||||
| Manage [subscriptions, and purchase storage and compute minutes](../subscriptions/gitlab_com/index.md) | | | | | ✓ |
|
||||
| Manage group-level custom roles | | | | | ✓ |
|
||||
|
||||
<!-- markdownlint-disable MD029 -->
|
||||
|
||||
|
|
|
|||
|
|
@ -116,6 +116,7 @@ to perform actions.
|
|||
Prerequisites:
|
||||
|
||||
- You must have the Owner or Maintainer role.
|
||||
- [Group membership lock](../../group/access_and_permissions.md#prevent-members-from-being-added-to-projects-in-a-group) must be disabled.
|
||||
|
||||
To add a user to a project:
|
||||
|
||||
|
|
|
|||
|
|
@ -378,6 +378,10 @@ module API
|
|||
authorize! :admin_group, user_group
|
||||
end
|
||||
|
||||
def authorize_admin_member_role!
|
||||
authorize! :admin_member_role, user_group
|
||||
end
|
||||
|
||||
def authorize_read_builds!
|
||||
authorize! :read_build, user_project
|
||||
end
|
||||
|
|
|
|||
|
|
@ -318,6 +318,7 @@ included_attributes:
|
|||
- :releases_access_level
|
||||
- :infrastructure_access_level
|
||||
- :model_experiments_access_level
|
||||
- :model_registry_access_level
|
||||
prometheus_metrics:
|
||||
- :created_at
|
||||
- :updated_at
|
||||
|
|
@ -738,6 +739,7 @@ included_attributes:
|
|||
- :releases_access_level
|
||||
- :infrastructure_access_level
|
||||
- :model_experiments_access_level
|
||||
- :model_registry_access_level
|
||||
- :auto_devops_deploy_strategy
|
||||
- :auto_devops_enabled
|
||||
- :container_registry_enabled
|
||||
|
|
|
|||
|
|
@ -9441,18 +9441,6 @@ msgstr ""
|
|||
msgid "CVE|Why Request a CVE ID?"
|
||||
msgstr ""
|
||||
|
||||
msgid "CVS|By enabling this feature, you accept the %{linkStart}Testing Terms of Use%{linkEnd}"
|
||||
msgstr ""
|
||||
|
||||
msgid "CVS|Continuous Vulnerability Scan"
|
||||
msgstr ""
|
||||
|
||||
msgid "CVS|Detect vulnerabilities outside a pipeline as new data is added to the GitLab Advisory Database."
|
||||
msgstr ""
|
||||
|
||||
msgid "CVS|Toggle CVS"
|
||||
msgstr ""
|
||||
|
||||
msgid "Cadence is not automated"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -37150,7 +37138,7 @@ msgstr ""
|
|||
msgid "Profiles|Using emoji in names seems fun, but please try to set a status message instead"
|
||||
msgstr ""
|
||||
|
||||
msgid "Profiles|Website url"
|
||||
msgid "Profiles|Website URL"
|
||||
msgstr ""
|
||||
|
||||
msgid "Profiles|Who you represent or work for."
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
source 'https://rubygems.org'
|
||||
|
||||
gem 'gitlab-qa', '~> 12', '>= 12.5.1', require: 'gitlab/qa'
|
||||
gem 'gitlab-qa', '~> 13', require: 'gitlab/qa'
|
||||
gem 'gitlab_quality-test_tooling', '~> 1.8.1', require: false
|
||||
gem 'gitlab-utils', path: '../gems/gitlab-utils'
|
||||
gem 'activesupport', '~> 7.0.8' # This should stay in sync with the root's Gemfile
|
||||
|
|
@ -26,7 +26,7 @@ gem 'rspec-parameterized', '~> 1.0.0'
|
|||
gem 'octokit', '~> 8.0.0'
|
||||
gem "faraday-retry", "~> 2.2"
|
||||
gem 'zeitwerk', '~> 2.6', '>= 2.6.12'
|
||||
gem 'influxdb-client', '~> 2.9'
|
||||
gem 'influxdb-client', '~> 3.0'
|
||||
gem 'terminal-table', '~> 3.0.2', require: false
|
||||
gem 'slack-notifier', '~> 2.4', require: false
|
||||
gem 'fog-google', '~> 1.19', require: false
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@ GEM
|
|||
gitlab (4.19.0)
|
||||
httparty (~> 0.20)
|
||||
terminal-table (>= 1.5.1)
|
||||
gitlab-qa (12.5.1)
|
||||
gitlab-qa (13.0.0)
|
||||
activesupport (>= 6.1, < 7.1)
|
||||
gitlab (~> 4.19)
|
||||
http (~> 5.0)
|
||||
|
|
@ -189,7 +189,7 @@ GEM
|
|||
httpclient (2.8.3)
|
||||
i18n (1.12.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
influxdb-client (2.9.0)
|
||||
influxdb-client (3.0.0)
|
||||
jwt (2.5.0)
|
||||
knapsack (4.0.0)
|
||||
rake
|
||||
|
|
@ -363,10 +363,10 @@ DEPENDENCIES
|
|||
faraday-retry (~> 2.2)
|
||||
fog-core (= 2.1.0)
|
||||
fog-google (~> 1.19)
|
||||
gitlab-qa (~> 12, >= 12.5.1)
|
||||
gitlab-qa (~> 13)
|
||||
gitlab-utils!
|
||||
gitlab_quality-test_tooling (~> 1.8.1)
|
||||
influxdb-client (~> 2.9)
|
||||
influxdb-client (~> 3.0)
|
||||
knapsack (~> 4.0)
|
||||
nokogiri (~> 1.15, >= 1.15.5)
|
||||
octokit (~> 8.0.0)
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ module RuboCop
|
|||
releases
|
||||
infrastructure
|
||||
model_experiments
|
||||
model_registry
|
||||
].freeze
|
||||
EE_FEATURES = %i[requirements].freeze
|
||||
ALL_FEATURES = (FEATURES + EE_FEATURES).freeze
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ FactoryBot.define do
|
|||
releases_access_level { ProjectFeature::ENABLED }
|
||||
infrastructure_access_level { ProjectFeature::ENABLED }
|
||||
model_experiments_access_level { ProjectFeature::ENABLED }
|
||||
model_registry_access_level { ProjectFeature::ENABLED }
|
||||
|
||||
# we can't assign the delegated `#ci_cd_settings` attributes directly, as the
|
||||
# `#ci_cd_settings` relation needs to be created first
|
||||
|
|
|
|||
|
|
@ -1,132 +0,0 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import { GlBadge, GlToggle } from '@gitlab/ui';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import Vue from 'vue';
|
||||
import ProjectSetContinuousVulnerabilityScanning from '~/security_configuration/graphql/project_set_continuous_vulnerability_scanning.graphql';
|
||||
import ContinuousVulnerabilityScan from '~/security_configuration/components/continuous_vulnerability_scan.vue';
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
|
||||
Vue.use(VueApollo);
|
||||
|
||||
const setCVSMockResponse = {
|
||||
data: {
|
||||
projectSetContinuousVulnerabilityScanning: {
|
||||
continuousVulnerabilityScanningEnabled: true,
|
||||
errors: [],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const defaultProvide = {
|
||||
continuousVulnerabilityScansEnabled: true,
|
||||
projectFullPath: 'project/full/path',
|
||||
};
|
||||
|
||||
describe('ContinuousVulnerabilityScan', () => {
|
||||
let wrapper;
|
||||
let apolloProvider;
|
||||
let requestHandlers;
|
||||
|
||||
const createComponent = (options) => {
|
||||
requestHandlers = {
|
||||
setCVSMutationHandler: jest.fn().mockResolvedValue(setCVSMockResponse),
|
||||
};
|
||||
|
||||
apolloProvider = createMockApollo([
|
||||
[ProjectSetContinuousVulnerabilityScanning, requestHandlers.setCVSMutationHandler],
|
||||
]);
|
||||
|
||||
wrapper = shallowMount(ContinuousVulnerabilityScan, {
|
||||
propsData: {
|
||||
feature: {
|
||||
available: true,
|
||||
configured: true,
|
||||
},
|
||||
},
|
||||
provide: {
|
||||
glFeatures: {
|
||||
dependencyScanningOnAdvisoryIngestion: true,
|
||||
globalDependencyScanningOnAdvisoryIngestion: false,
|
||||
},
|
||||
...defaultProvide,
|
||||
},
|
||||
apolloProvider,
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
apolloProvider = null;
|
||||
});
|
||||
|
||||
const findBadge = () => wrapper.findComponent(GlBadge);
|
||||
const findToggle = () => wrapper.findComponent(GlToggle);
|
||||
|
||||
it('renders the component', () => {
|
||||
expect(wrapper.exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('renders the correct title', () => {
|
||||
expect(wrapper.text()).toContain('Continuous Vulnerability Scan');
|
||||
});
|
||||
|
||||
it('renders the badge and toggle component with correct values', () => {
|
||||
expect(findBadge().exists()).toBe(true);
|
||||
expect(findBadge().text()).toBe('Experiment');
|
||||
|
||||
expect(findToggle().exists()).toBe(true);
|
||||
expect(findToggle().props('value')).toBe(defaultProvide.continuousVulnerabilityScansEnabled);
|
||||
});
|
||||
|
||||
it('should disable toggle when feature is not configured', () => {
|
||||
createComponent({
|
||||
propsData: {
|
||||
feature: {
|
||||
available: true,
|
||||
configured: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(findToggle().props('disabled')).toBe(true);
|
||||
});
|
||||
|
||||
it('calls mutation on toggle change with correct payload', () => {
|
||||
findToggle().vm.$emit('change', true);
|
||||
|
||||
expect(requestHandlers.setCVSMutationHandler).toHaveBeenCalledWith({
|
||||
input: {
|
||||
projectPath: 'project/full/path',
|
||||
enable: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
describe('when feature flag is disabled', () => {
|
||||
it.each`
|
||||
dependencyScanningOnAdvisoryIngestion | globalDependencyScanningOnAdvisoryIngestion
|
||||
${false} | ${false}
|
||||
${true} | ${true}
|
||||
${false} | ${true}
|
||||
`(
|
||||
'when dependencyScanningOnAdvisoryIngestion: `$dependencyScanningOnAdvisoryIngestion` and globalDependencyScanningOnAdvisoryIngestion: `$globalDependencyScanningOnAdvisoryIngestion` should not render toggle and badge',
|
||||
({ dependencyScanningOnAdvisoryIngestion, globalDependencyScanningOnAdvisoryIngestion }) => {
|
||||
createComponent({
|
||||
provide: {
|
||||
glFeatures: {
|
||||
dependencyScanningOnAdvisoryIngestion,
|
||||
globalDependencyScanningOnAdvisoryIngestion,
|
||||
},
|
||||
...defaultProvide,
|
||||
},
|
||||
});
|
||||
|
||||
expect(findToggle().exists()).toBe(false);
|
||||
expect(findBadge().exists()).toBe(false);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
import { GlIcon } from '@gitlab/ui';
|
||||
import { mount } from '@vue/test-utils';
|
||||
import Vue from 'vue';
|
||||
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
|
||||
import { securityFeatures } from '~/security_configuration/components/constants';
|
||||
import FeatureCard from '~/security_configuration/components/feature_card.vue';
|
||||
|
|
@ -14,10 +13,6 @@ import {
|
|||
import { manageViaMRErrorMessage } from '../constants';
|
||||
import { makeFeature } from './utils';
|
||||
|
||||
const MockComponent = Vue.component('MockComponent', {
|
||||
render: (createElement) => createElement('span'),
|
||||
});
|
||||
|
||||
describe('FeatureCard component', () => {
|
||||
let feature;
|
||||
let wrapper;
|
||||
|
|
@ -394,17 +389,4 @@ describe('FeatureCard component', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when a slot component is passed', () => {
|
||||
beforeEach(() => {
|
||||
feature = makeFeature({
|
||||
slotComponent: MockComponent,
|
||||
});
|
||||
createComponent({ feature });
|
||||
});
|
||||
|
||||
it('renders the component properly', () => {
|
||||
expect(wrapper.findComponent(MockComponent).exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -86,46 +86,47 @@ RSpec.describe Mutations::DesignManagement::Delete do
|
|||
end
|
||||
end
|
||||
|
||||
it 'runs no more than 31 queries' do
|
||||
it 'runs no more than 34 queries' do
|
||||
allow(Gitlab::Tracking).to receive(:event) # rubocop:disable RSpec/ExpectGitlabTracking
|
||||
|
||||
filenames.each(&:present?) # ignore setup
|
||||
# Queries: as of 2022-09-08
|
||||
# Queries: as of 2022-12-01
|
||||
# -------------
|
||||
# 01. routing query
|
||||
# 02. policy query: find namespace by type and id
|
||||
# 03. policy query: find namespace by id
|
||||
# 04. policy query: project.project_feature
|
||||
# 05,06. project.authorizations for user (same query twice)
|
||||
# 07. find issue by iid
|
||||
# 08. find project by id
|
||||
# 09. find namespace by id
|
||||
# 10. find group namespace by id
|
||||
# 11. policy query: find namespace by id (same query as 3)
|
||||
# 12. project.authorizations for user (same query as 5)
|
||||
# 13. find user by id
|
||||
# 14. project.project_features (same query as 3)
|
||||
# 15. project.authorizations for user (same query as 5)
|
||||
# 16. current designs by filename and issue
|
||||
# 17, 18 project.authorizations for user (same query as 5)
|
||||
# 19. find design_management_repository for project
|
||||
# 20. find route by id and source_type
|
||||
# 01. for routes to find routes.source_id of projects matching paths
|
||||
# 02. Find projects with the above source id.
|
||||
# 03. preload routes of the above projects
|
||||
# 04. policy query: find namespace by type and id
|
||||
# 05. policy query: namespace_bans
|
||||
# 06. policy query: project.project_feature
|
||||
# 07,08. project.authorizations for user (same query twice)
|
||||
# 09. find issue by iid
|
||||
# 10. find project by id
|
||||
# 11. find namespace by id
|
||||
# 12. policy query: find namespace by type and id (same query as 4)
|
||||
# 13. project.authorizations for user (same query as 7)
|
||||
# 14. find user by id
|
||||
# 15. project.project_features (same query as 6)
|
||||
# 16. project.authorizations for user (same query as 7)
|
||||
# 17. current designs by filename and issue
|
||||
# 18, 19 project.authorizations for user (same query as 7)
|
||||
# 20. find design_management_repository for project
|
||||
# 21. find route by source_id and source_type
|
||||
# ------------- our queries are below:
|
||||
# 21. start transaction
|
||||
# 22. create version with sha and issue
|
||||
# 23. create design-version links
|
||||
# 24. validate version.actions.present?
|
||||
# 25. validate version.sha is unique
|
||||
# 26. validate version.issue.present?
|
||||
# 27. leave transaction
|
||||
# 28. find project by id (same query as 8)
|
||||
# 29. find namespace by id (same query as 9)
|
||||
# 30. find project by id (same query as 8)
|
||||
# 31. find project by id (same query as 8)
|
||||
# 32. create event
|
||||
# 33. find plan for standard context
|
||||
# 22. start transaction
|
||||
# 23. create version with sha and issue
|
||||
# 24. create design-version links
|
||||
# 25. validate version.actions.present?
|
||||
# 26. validate version.sha is unique
|
||||
# 27. validate version.issue.present?
|
||||
# 28. leave transaction
|
||||
# 29. find project by id (same query as 10)
|
||||
# 30. find namespace by id (same query as 11)
|
||||
# 31. find project by id (same query as 10)
|
||||
# 32. find project by id (same query as 10)
|
||||
# 33. create event
|
||||
# 34. find plan for standard context
|
||||
#
|
||||
expect { run_mutation }.not_to exceed_query_limit(33)
|
||||
expect { run_mutation }.not_to exceed_query_limit(34)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ RSpec.describe Resolvers::GroupResolver do
|
|||
it 'batch-resolves groups by full path' do
|
||||
paths = [group1.full_path, group2.full_path]
|
||||
|
||||
result = batch_sync(max_queries: 1) do
|
||||
result = batch_sync(max_queries: 3) do
|
||||
paths.map { |path| resolve_group(path) }
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ RSpec.describe Resolvers::ProjectResolver do
|
|||
it 'batch-resolves projects by full path' do
|
||||
paths = [project1.full_path, project2.full_path]
|
||||
|
||||
result = batch_sync(max_queries: 1) do
|
||||
result = batch_sync(max_queries: 3) do
|
||||
paths.map { |path| resolve_project(path) }
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -240,9 +240,15 @@ RSpec.describe Banzai::Filter::References::AlertReferenceFilter, feature_categor
|
|||
|
||||
# Since we're not batching alert queries across projects,
|
||||
# we have to account for that.
|
||||
# 1 for both projects, 1 for alerts in each project == 3
|
||||
# 1 for routes to find routes.source_id of projects matching paths
|
||||
# 1 for projects belonging to the above routes
|
||||
# 1 for preloading routes of the projects
|
||||
# 1 for loading the namespaces associated to the project
|
||||
# 1 for loading the routes associated with the namespace
|
||||
# 1x2 for alerts in each project
|
||||
# Total == 7
|
||||
# TODO: https://gitlab.com/gitlab-org/gitlab/-/issues/330359
|
||||
max_count += 2
|
||||
max_count += 6
|
||||
|
||||
expect do
|
||||
reference_filter(markdown)
|
||||
|
|
|
|||
|
|
@ -287,12 +287,18 @@ RSpec.describe Banzai::Filter::References::CommitReferenceFilter, feature_catego
|
|||
reference_filter(markdown)
|
||||
end.count
|
||||
|
||||
markdown = "#{commit_reference} 8b95f2f1 8b95f2f2 8b95f2f3 #{commit2_reference} #{commit3_reference}"
|
||||
expect(max_count).to eq 0
|
||||
|
||||
markdown = "#{commit_reference} 8b95f2f1 8b95f2f2 8b95f2f3 #{commit2_reference} #{commit3_reference}"
|
||||
|
||||
# Commits are not DB entries, they are on the project itself.
|
||||
# So adding commits from two more projects to the markdown should
|
||||
# only increase by 1 query
|
||||
max_count += 1
|
||||
# 1 for for routes to find routes.source_id of projects matching paths
|
||||
# 1 for projects belonging to the above routes
|
||||
# 1 for preloading routes of the projects
|
||||
# 1 for loading the namespaces associated to the project
|
||||
# 1 for loading the routes associated with the namespace
|
||||
# Total = 5
|
||||
max_count += 5
|
||||
|
||||
expect do
|
||||
reference_filter(markdown)
|
||||
|
|
|
|||
|
|
@ -747,10 +747,16 @@ RSpec.describe Banzai::Filter::References::LabelReferenceFilter, feature_categor
|
|||
# Since we're not batching label queries across projects/groups,
|
||||
# queries increase when a new project/group is added.
|
||||
# TODO: https://gitlab.com/gitlab-org/gitlab/-/issues/330359
|
||||
# first reference to already loaded project (1),
|
||||
# second reference requires project and namespace (2), and label (1)
|
||||
# 1 for for routes to find routes.source_id of projects matching paths
|
||||
# 1 for projects belonging to the above routes
|
||||
# 1 for preloading routes of the projects
|
||||
# 1 for loading the namespaces associated to the project
|
||||
# 1 for loading the routes associated with the namespace
|
||||
# 1 for the group
|
||||
# 1x2 for labels
|
||||
# Total == 8
|
||||
markdown = "#{project_reference} #{group2_reference}"
|
||||
max_count = control_count + 3
|
||||
max_count = control_count + 7
|
||||
|
||||
expect do
|
||||
reference_filter(markdown)
|
||||
|
|
|
|||
|
|
@ -522,7 +522,7 @@ RSpec.describe Banzai::Filter::References::MilestoneReferenceFilter, feature_cat
|
|||
# queries increase when a new project/group is added.
|
||||
# TODO: https://gitlab.com/gitlab-org/gitlab/-/issues/330359
|
||||
markdown = "#{project_reference} #{group2_reference}"
|
||||
control_count += 5
|
||||
control_count += 9
|
||||
|
||||
expect do
|
||||
reference_filter(markdown)
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ RSpec.describe Banzai::Filter::References::ProjectReferenceFilter, feature_categ
|
|||
reference_filter(markdown)
|
||||
end.count
|
||||
|
||||
expect(max_count).to eq 1
|
||||
expect(max_count).to eq 2
|
||||
|
||||
markdown = "#{normal_project_reference} #{invalidate_reference(normal_project_reference)} #{group_project_reference} #{nested_project_reference}"
|
||||
|
||||
|
|
|
|||
|
|
@ -79,8 +79,16 @@ RSpec.describe Banzai::Filter::References::ReferenceCache, feature_category: :te
|
|||
expect(control_count).to eq 3
|
||||
# Since this is an issue filter that is not batching issue queries
|
||||
# across projects, we have to account for that.
|
||||
# 1 for original issue, 2 for second route/project, 1 for other issue
|
||||
max_count = control_count + 4
|
||||
# 1 for for routes to find routes.source_id of projects matching paths
|
||||
# 1 for projects belonging to the above routes
|
||||
# 1 for preloading routes of the projects
|
||||
# 1 for loading the namespaces associated to the project
|
||||
# 1 for loading the routes associated with the namespace
|
||||
# 1x2 for issues
|
||||
# 1x2 for groups
|
||||
# 1x2 for work_item_types
|
||||
# Total = 11
|
||||
max_count = control_count + 8
|
||||
|
||||
expect do
|
||||
cache.load_references_per_parent(filter.nodes)
|
||||
|
|
|
|||
|
|
@ -239,9 +239,15 @@ RSpec.describe Banzai::Filter::References::SnippetReferenceFilter, feature_categ
|
|||
|
||||
# Since we're not batching snippet queries across projects,
|
||||
# we have to account for that.
|
||||
# 1 for both projects, 1 for snippets in each project == 3
|
||||
# 1 for for routes to find routes.source_id of projects matching paths
|
||||
# 1 for projects belonging to the above routes
|
||||
# 1 for preloading routes of the projects
|
||||
# 1 for loading the namespaces associated to the project
|
||||
# 1 for loading the routes associated with the namespace
|
||||
# 1x2 for snippets in each project == 2
|
||||
# Total = 7
|
||||
# TODO: https://gitlab.com/gitlab-org/gitlab/-/issues/330359
|
||||
max_count = control_count + 2
|
||||
max_count = control_count + 6
|
||||
|
||||
expect do
|
||||
reference_filter(markdown)
|
||||
|
|
|
|||
|
|
@ -706,6 +706,7 @@ ProjectFeature:
|
|||
- monitor_access_level
|
||||
- infrastructure_access_level
|
||||
- model_experiments_access_level
|
||||
- model_registry_access_level
|
||||
- created_at
|
||||
- updated_at
|
||||
ProtectedBranch::MergeAccessLevel:
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.shared_examples 'routable resource' do
|
||||
shared_examples_for '.find_by_full_path' do |has_cross_join: false|
|
||||
shared_examples_for '.find_by_full_path' do
|
||||
it 'finds records by their full path' do
|
||||
expect(described_class.find_by_full_path(record.full_path)).to eq(record)
|
||||
expect(described_class.find_by_full_path(record.full_path.upcase)).to eq(record)
|
||||
|
|
@ -46,22 +46,98 @@ RSpec.shared_examples 'routable resource' do
|
|||
end
|
||||
end
|
||||
|
||||
if has_cross_join
|
||||
it 'has a cross-join' do
|
||||
expect(Gitlab::Database).to receive(:allow_cross_joins_across_databases)
|
||||
it 'does not have cross-join' do
|
||||
expect(Gitlab::Database).not_to receive(:allow_cross_joins_across_databases)
|
||||
|
||||
described_class.find_by_full_path(record.full_path)
|
||||
end
|
||||
else
|
||||
it 'does not have cross-join' do
|
||||
expect(Gitlab::Database).not_to receive(:allow_cross_joins_across_databases)
|
||||
|
||||
described_class.find_by_full_path(record.full_path)
|
||||
end
|
||||
described_class.find_by_full_path(record.full_path)
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like '.find_by_full_path', :aggregate_failures
|
||||
|
||||
shared_examples_for '.where_full_path_in' do
|
||||
context 'without any paths' do
|
||||
it 'returns an empty relation' do
|
||||
expect(described_class.where_full_path_in([])).to eq([])
|
||||
end
|
||||
end
|
||||
|
||||
context 'without any valid paths' do
|
||||
it 'returns an empty relation' do
|
||||
expect(described_class.where_full_path_in(%w[unknown])).to eq([])
|
||||
end
|
||||
end
|
||||
|
||||
context 'with valid paths' do
|
||||
it 'returns the entities matching the paths' do
|
||||
result = described_class.where_full_path_in([record.full_path, record_2.full_path])
|
||||
|
||||
expect(result).to contain_exactly(record, record_2)
|
||||
end
|
||||
|
||||
it 'returns entities regardless of the casing of paths' do
|
||||
result = described_class.where_full_path_in([record.full_path.upcase, record_2.full_path.upcase])
|
||||
|
||||
expect(result).to contain_exactly(record, record_2)
|
||||
end
|
||||
end
|
||||
|
||||
context 'on the usage of `use_includes` parameter' do
|
||||
let_it_be(:klass) { record.class.to_s.downcase }
|
||||
let_it_be(:record_3) { create(:"#{klass}") }
|
||||
let_it_be(:record_4) { create(:"#{klass}") }
|
||||
|
||||
context 'when use_includes: true' do
|
||||
it 'includes route information when loading records' do
|
||||
control_count = ActiveRecord::QueryRecorder.new do
|
||||
described_class.where_full_path_in([record.full_path, record_2.full_path], use_includes: true)
|
||||
.map(&:route)
|
||||
end
|
||||
|
||||
expect do
|
||||
described_class.where_full_path_in(
|
||||
[
|
||||
record.full_path,
|
||||
record_2.full_path,
|
||||
record_3.full_path,
|
||||
record_4.full_path
|
||||
], use_includes: true)
|
||||
.map(&:route)
|
||||
end.to issue_same_number_of_queries_as(control_count)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when use_includes: false' do
|
||||
it 'does not include route information when loading records' do
|
||||
control_count = ActiveRecord::QueryRecorder.new do
|
||||
described_class.where_full_path_in([record.full_path, record_2.full_path], use_includes: false)
|
||||
.map(&:route)
|
||||
end
|
||||
|
||||
expect do
|
||||
described_class.where_full_path_in(
|
||||
[
|
||||
record.full_path,
|
||||
record_2.full_path,
|
||||
record_3.full_path,
|
||||
record_4.full_path
|
||||
], use_includes: false)
|
||||
.map(&:route)
|
||||
end.not_to issue_same_number_of_queries_as(control_count)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like '.where_full_path_in', :aggregate_failures
|
||||
|
||||
context 'when the `optimize_where_full_path_in` feature flag is turned OFF' do
|
||||
before do
|
||||
stub_feature_flags(optimize_where_full_path_in: false)
|
||||
end
|
||||
|
||||
it_behaves_like '.where_full_path_in', :aggregate_failures
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.shared_examples 'routable resource with parent' do
|
||||
|
|
@ -105,10 +181,12 @@ RSpec.describe Group, 'Routable', :with_clean_rails_cache, feature_category: :gr
|
|||
|
||||
it_behaves_like 'routable resource' do
|
||||
let_it_be(:record) { group }
|
||||
let_it_be(:record_2) { nested_group }
|
||||
end
|
||||
|
||||
it_behaves_like 'routable resource with parent' do
|
||||
let_it_be(:record) { nested_group }
|
||||
let_it_be(:record_2) { group }
|
||||
end
|
||||
|
||||
describe 'Validations' do
|
||||
|
|
@ -169,34 +247,6 @@ RSpec.describe Group, 'Routable', :with_clean_rails_cache, feature_category: :gr
|
|||
expect(group.route.namespace).to eq(group)
|
||||
end
|
||||
|
||||
describe '.where_full_path_in' do
|
||||
context 'without any paths' do
|
||||
it 'returns an empty relation' do
|
||||
expect(described_class.where_full_path_in([])).to eq([])
|
||||
end
|
||||
end
|
||||
|
||||
context 'without any valid paths' do
|
||||
it 'returns an empty relation' do
|
||||
expect(described_class.where_full_path_in(%w[unknown])).to eq([])
|
||||
end
|
||||
end
|
||||
|
||||
context 'with valid paths' do
|
||||
it 'returns the projects matching the paths' do
|
||||
result = described_class.where_full_path_in([group.to_param, nested_group.to_param])
|
||||
|
||||
expect(result).to contain_exactly(group, nested_group)
|
||||
end
|
||||
|
||||
it 'returns projects regardless of the casing of paths' do
|
||||
result = described_class.where_full_path_in([group.to_param.upcase, nested_group.to_param.upcase])
|
||||
|
||||
expect(result).to contain_exactly(group, nested_group)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#parent_loaded?' do
|
||||
before do
|
||||
group.parent = create(:group)
|
||||
|
|
@ -232,9 +282,11 @@ end
|
|||
RSpec.describe Project, 'Routable', :with_clean_rails_cache, feature_category: :groups_and_projects do
|
||||
let_it_be(:namespace) { create(:namespace) }
|
||||
let_it_be(:project) { create(:project, namespace: namespace) }
|
||||
let_it_be(:project_2) { create(:project) }
|
||||
|
||||
it_behaves_like 'routable resource with parent' do
|
||||
let_it_be(:record) { project }
|
||||
let_it_be(:record_2) { project_2 }
|
||||
end
|
||||
|
||||
it 'creates route with namespace referencing project namespace' do
|
||||
|
|
@ -252,6 +304,17 @@ RSpec.describe Project, 'Routable', :with_clean_rails_cache, feature_category: :
|
|||
expect(record).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe '.where_full_path_in' do
|
||||
it 'does not return records if the sources are different, but the IDs match' do
|
||||
group = create(:group, id: 1992)
|
||||
project = create(:project, id: 1992)
|
||||
|
||||
records = described_class.where(id: project.id).where_full_path_in([group.full_path])
|
||||
|
||||
expect(records).to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.describe Namespaces::ProjectNamespace, 'Routable', :with_clean_rails_cache, feature_category: :groups_and_projects do
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ RSpec.describe ProjectFeature, feature_category: :groups_and_projects do
|
|||
specify { expect(subject.package_registry_access_level).to eq(ProjectFeature::ENABLED) }
|
||||
specify { expect(subject.container_registry_access_level).to eq(ProjectFeature::ENABLED) }
|
||||
specify { expect(subject.model_experiments_access_level).to eq(ProjectFeature::ENABLED) }
|
||||
specify { expect(subject.model_registry_access_level).to eq(ProjectFeature::ENABLED) }
|
||||
end
|
||||
|
||||
describe 'PRIVATE_FEATURES_MIN_ACCESS_LEVEL_FOR_PRIVATE_PROJECT' do
|
||||
|
|
|
|||
|
|
@ -1128,6 +1128,7 @@ RSpec.describe Project, factory_default: :keep, feature_category: :groups_and_pr
|
|||
it { is_expected.to delegate_method(:container_registry_access_level).to(:project_feature) }
|
||||
it { is_expected.to delegate_method(:environments_access_level).to(:project_feature) }
|
||||
it { is_expected.to delegate_method(:model_experiments_access_level).to(:project_feature) }
|
||||
it { is_expected.to delegate_method(:model_registry_access_level).to(:project_feature) }
|
||||
it { is_expected.to delegate_method(:feature_flags_access_level).to(:project_feature) }
|
||||
it { is_expected.to delegate_method(:releases_access_level).to(:project_feature) }
|
||||
it { is_expected.to delegate_method(:infrastructure_access_level).to(:project_feature) }
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@ RSpec.describe 'Setting assignees of a merge request', :assume_throttled, featur
|
|||
context 'when passing append as true' do
|
||||
let(:mode) { Types::MutationOperationModeEnum.enum[:append] }
|
||||
let(:input) { { assignee_usernames: [assignee2.username], operation_mode: mode } }
|
||||
let(:db_query_limit) { 23 }
|
||||
let(:db_query_limit) { 25 }
|
||||
|
||||
before do
|
||||
# In CE, APPEND is a NOOP as you can't have multiple assignees
|
||||
|
|
@ -147,7 +147,7 @@ RSpec.describe 'Setting assignees of a merge request', :assume_throttled, featur
|
|||
end
|
||||
|
||||
context 'when passing remove as true' do
|
||||
let(:db_query_limit) { 31 }
|
||||
let(:db_query_limit) { 33 }
|
||||
let(:mode) { Types::MutationOperationModeEnum.enum[:remove] }
|
||||
let(:input) { { assignee_usernames: [assignee.username], operation_mode: mode } }
|
||||
let(:expected_result) { [] }
|
||||
|
|
|
|||
|
|
@ -133,6 +133,7 @@ project_feature:
|
|||
- project_id
|
||||
- updated_at
|
||||
- operations_access_level
|
||||
- model_registry_access_level
|
||||
computed_attributes:
|
||||
- issues_enabled
|
||||
- jobs_enabled
|
||||
|
|
|
|||
|
|
@ -75,6 +75,58 @@ RSpec.describe Projects::PipelinesController, feature_category: :continuous_inte
|
|||
expect(response).to have_gitlab_http_status(:ok)
|
||||
end
|
||||
|
||||
context 'when pipeline_stage_set_last_modified is disabled' do
|
||||
before do
|
||||
stub_feature_flags(pipeline_stage_set_last_modified: false)
|
||||
end
|
||||
|
||||
it 'does not set Last-Modified' do
|
||||
create(:ci_build, :retried, :failed, pipeline: pipeline, stage: 'build')
|
||||
|
||||
request_build_stage
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(response.headers['Last-Modified']).to be_nil
|
||||
expect(response.headers['Cache-Control']).to eq('max-age=0, private, must-revalidate')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when pipeline_stage_set_last_modified is enabled' do
|
||||
before do
|
||||
stub_feature_flags(pipeline_stage_set_last_modified: true)
|
||||
stage.statuses.update_all(updated_at: status_timestamp)
|
||||
end
|
||||
|
||||
let(:last_modified) { DateTime.parse(response.headers['Last-Modified']).utc }
|
||||
let(:cache_control) { response.headers['Cache-Control'] }
|
||||
|
||||
context 'when status.updated_at is before stage.updated' do
|
||||
let(:stage) { pipeline.stage('build') }
|
||||
let(:status_timestamp) { stage.updated_at - 10.minutes }
|
||||
|
||||
it 'sets correct Last-Modified of stage.updated_at' do
|
||||
request_build_stage
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(last_modified).to be_within(1.second).of stage.updated_at
|
||||
expect(cache_control).to eq('max-age=86400, private')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when status.updated_at is after stage.updated' do
|
||||
let(:stage) { pipeline.stage('build') }
|
||||
let(:status_timestamp) { stage.updated_at + 10.minutes }
|
||||
|
||||
it 'sets correct Last-Modified of max(status.updated_at)' do
|
||||
request_build_stage
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(last_modified).to be_within(1.second).of status_timestamp
|
||||
expect(cache_control).to eq('max-age=86400, private')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with retried builds' do
|
||||
it 'does not execute N+1 queries' do
|
||||
create(:ci_build, :retried, :failed, pipeline: pipeline, stage: 'build')
|
||||
|
|
|
|||
Loading…
Reference in New Issue