Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2023-12-06 09:12:47 +00:00
parent 50b6f6a788
commit 08608c8e9e
52 changed files with 395 additions and 430 deletions

View File

@ -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]

View File

@ -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,

View File

@ -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>

View File

@ -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>

View File

@ -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, {

View File

@ -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)

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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')

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -511,8 +511,6 @@
- 1
- - package_cleanup
- 1
- - package_metadata_advisory_scan
- 1
- - package_metadata_global_advisory_scan
- 1
- - package_repositories

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -0,0 +1 @@
5c9d89f5d5401d6a7082d5790cb12ee610a0a06138cf3608534a09685c812ea8

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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 -->

View File

@ -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:

View File

@ -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

View File

@ -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

View File

@ -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."

View File

@ -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

View File

@ -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)

View File

@ -29,6 +29,7 @@ module RuboCop
releases
infrastructure
model_experiments
model_registry
].freeze
EE_FEATURES = %i[requirements].freeze
ALL_FEATURES = (FEATURES + EE_FEATURES).freeze

View File

@ -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

View File

@ -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);
},
);
});
});

View File

@ -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);
});
});
});

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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}"

View File

@ -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)

View File

@ -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)

View File

@ -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:

View File

@ -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

View File

@ -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

View File

@ -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) }

View File

@ -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) { [] }

View File

@ -133,6 +133,7 @@ project_feature:
- project_id
- updated_at
- operations_access_level
- model_registry_access_level
computed_attributes:
- issues_enabled
- jobs_enabled

View File

@ -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')