Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-01-31 12:17:41 +00:00
parent 1edfc04d9f
commit 8bdb37de3a
34 changed files with 1062 additions and 71 deletions

View file

@ -95,8 +95,7 @@ rules:
order: ignore
overrides:
- files:
- 'ee/spec/frontend*/**/*'
- 'spec/frontend*/**/*'
- '{,ee/,jh/}spec/frontend*/**/*'
rules:
'@gitlab/require-i18n-strings': off
'@gitlab/no-runtime-template-compiler': off

View file

@ -1 +1 @@
13.23.0
13.23.1

View file

@ -21,13 +21,17 @@ export default {
},
},
computed: {
showModuleCount() {
return Number.isInteger(this.count);
hasModules() {
return Number.isInteger(this.count) && this.count > 0;
},
moduleAmountText() {
return n__(`%d Module`, `%d Modules`, this.count);
},
infoMessages() {
if (!this.hasModules) {
return [];
}
return [{ text: this.$options.i18n.LIST_INTRO_TEXT, link: this.helpUrl }];
},
},
@ -43,11 +47,7 @@ export default {
<template>
<title-area :title="$options.i18n.LIST_TITLE_TEXT" :info-messages="infoMessages">
<template #metadata-amount>
<metadata-item
v-if="showModuleCount"
icon="infrastructure-registry"
:text="moduleAmountText"
/>
<metadata-item v-if="hasModules" icon="infrastructure-registry" :text="moduleAmountText" />
</template>
</title-area>
</template>

View file

@ -99,7 +99,7 @@ export default {
<template>
<div>
<infrastructure-title :help-url="packageHelpUrl" :count="packagesCount" />
<infrastructure-search @update="requestPackagesList" />
<infrastructure-search v-if="packagesCount > 0" @update="requestPackagesList" />
<package-list @page:changed="onPageChanged" @package:delete="onPackageDeleteRequest">
<template #empty-state>

View file

@ -38,6 +38,8 @@ class GroupsController < Groups::ApplicationController
before_action :check_export_rate_limit!, only: [:export, :download_export]
before_action :track_experiment_event, only: [:new]
helper_method :captcha_required?
skip_cross_project_access_check :index, :new, :create, :edit, :update,
@ -378,6 +380,12 @@ class GroupsController < Groups::ApplicationController
def captcha_required?
captcha_enabled? && !params[:parent_id]
end
def track_experiment_event
return if params[:parent_id]
experiment(:require_verification_for_namespace_creation, user: current_user).track(:start_create_group)
end
end
GroupsController.prepend_mod_with('GroupsController')

View file

@ -71,6 +71,7 @@ class Projects::IssuesController < Projects::ApplicationController
]
feature_category :service_desk, [:service_desk]
urgency :low, [:service_desk]
feature_category :importers, [:import_csv, :export_csv]
attr_accessor :vulnerability_id

View file

@ -4,6 +4,7 @@ class Projects::ServiceDeskController < Projects::ApplicationController
before_action :authorize_admin_project!
feature_category :service_desk
urgency :low
def show
json_response

View file

@ -1,6 +1,10 @@
# frozen_string_literal: true
class RequireVerificationForNamespaceCreationExperiment < ApplicationExperiment # rubocop:disable Gitlab/NamespacedClass
exclude :existing_user
EXPERIMENT_START_DATE = Date.new(2022, 1, 31)
def control_behavior
false
end
@ -24,4 +28,10 @@ class RequireVerificationForNamespaceCreationExperiment < ApplicationExperiment
def subject
context.value[:user]
end
def existing_user
return false unless user_or_actor
user_or_actor.created_at < EXPERIMENT_START_DATE
end
end

View file

@ -139,7 +139,7 @@ module GroupsHelper
{}
end
def require_verification_for_group_creation_enabled?
def require_verification_for_namespace_creation_enabled?
# overridden in EE
false
end

View file

@ -27,10 +27,10 @@
%li= _('Runner tokens')
%li= _('SAML discovery tokens')
- if group.export_file_exists?
= link_to _('Regenerate export'), export_group_path(group),
method: :post, class: 'btn gl-button btn-default', data: { qa_selector: 'regenerate_export_group_link' }
= link_to _('Download export'), download_export_group_path(group),
rel: 'nofollow', method: :get, class: 'btn gl-button btn-default', data: { qa_selector: 'download_export_link' }
= link_to _('Regenerate export'), export_group_path(group),
method: :post, class: 'btn gl-button btn-default', data: { qa_selector: 'regenerate_export_group_link' }
- else
= link_to _('Export group'), export_group_path(group),
method: :post, class: 'btn gl-button btn-default', data: { qa_selector: 'export_group_link' }

View file

@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/350465
milestone: '14.7'
type: development
group: group::source code
default_enabled: false
default_enabled: true

View file

@ -1,8 +0,0 @@
---
name: require_verification_for_group_creation
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/77569
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/349857
milestone: '14.7'
type: experiment
group: group::activation
default_enabled: false

View file

@ -0,0 +1,13 @@
- name: "Querying Usage Trends via the `instanceStatisticsMeasurements` GraphQL node"
announcement_milestone: "14.8"
announcement_date: "2022-02-22"
removal_milestone: "15.0"
removal_date: "2022-05-22"
breaking_change: true
body: | # Do not modify this line, instead modify the lines below.
The `instanceStatisticsMeasurements` GraphQL node has been renamed to `usageTrendsMeasurements` in 13.10 and the old field name has been marked as deprecated. To fix the existing GraphQL queries, replace `instanceStatisticsMeasurements` with `usageTrendsMeasurements`.
# The following items are not published on the docs page, but may be used in the future.
stage: Manage
tiers: [FREE]
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/332323
documentation_url: https://docs.gitlab.com/ee/api/graphql/reference/index.html#queryusagetrendsmeasurements

View file

@ -0,0 +1,58 @@
# frozen_string_literal: true
class AddInsertOrUpdateVulnerabilityReadsTrigger < Gitlab::Database::Migration[1.0]
include Gitlab::Database::SchemaHelpers
FUNCTION_NAME = 'insert_or_update_vulnerability_reads'
TRIGGER_NAME = 'trigger_insert_or_update_vulnerability_reads_from_occurrences'
def up
execute(<<~SQL)
CREATE OR REPLACE FUNCTION #{FUNCTION_NAME}()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
DECLARE
severity smallint;
state smallint;
report_type smallint;
resolved_on_default_branch boolean;
BEGIN
IF (NEW.vulnerability_id IS NULL AND (TG_OP = 'INSERT' OR TG_OP = 'UPDATE')) THEN
RETURN NULL;
END IF;
IF (TG_OP = 'UPDATE' AND OLD.vulnerability_id IS NOT NULL AND NEW.vulnerability_id IS NOT NULL) THEN
RETURN NULL;
END IF;
SELECT
vulnerabilities.severity, vulnerabilities.state, vulnerabilities.report_type, vulnerabilities.resolved_on_default_branch
INTO
severity, state, report_type, resolved_on_default_branch
FROM
vulnerabilities
WHERE
vulnerabilities.id = NEW.vulnerability_id;
INSERT INTO vulnerability_reads (vulnerability_id, project_id, scanner_id, report_type, severity, state, resolved_on_default_branch, uuid, location_image, cluster_agent_id)
VALUES (NEW.vulnerability_id, NEW.project_id, NEW.scanner_id, report_type, severity, state, resolved_on_default_branch, NEW.uuid::uuid, NEW.location->>'image', NEW.location->'kubernetes_resource'->>'agent_id')
ON CONFLICT(vulnerability_id) DO NOTHING;
RETURN NULL;
END
$$;
SQL
execute(<<~SQL)
CREATE TRIGGER #{TRIGGER_NAME}
AFTER INSERT OR UPDATE ON vulnerability_occurrences
FOR EACH ROW
EXECUTE PROCEDURE #{FUNCTION_NAME}();
SQL
end
def down
drop_trigger(:vulnerability_occurrences, TRIGGER_NAME)
drop_function(FUNCTION_NAME)
end
end

View file

@ -0,0 +1,40 @@
# frozen_string_literal: true
class AddUpdateVulnerabilityReadsTrigger < Gitlab::Database::Migration[1.0]
include Gitlab::Database::SchemaHelpers
TRIGGER_NAME = 'trigger_update_vulnerability_reads_on_vulnerability_update'
FUNCTION_NAME = 'update_vulnerability_reads_from_vulnerability'
def up
create_trigger_function(FUNCTION_NAME, replace: true) do
<<~SQL
UPDATE
vulnerability_reads
SET
severity = NEW.severity,
state = NEW.state,
resolved_on_default_branch = NEW.resolved_on_default_branch
WHERE vulnerability_id = NEW.id;
RETURN NULL;
SQL
end
execute(<<~SQL)
CREATE TRIGGER #{TRIGGER_NAME}
AFTER UPDATE ON vulnerabilities
FOR EACH ROW
WHEN (
OLD.severity IS DISTINCT FROM NEW.severity OR
OLD.state IS DISTINCT FROM NEW.state OR
OLD.resolved_on_default_branch IS DISTINCT FROM NEW.resolved_on_default_branch
)
EXECUTE PROCEDURE #{FUNCTION_NAME}();
SQL
end
def down
drop_trigger(:vulnerabilities, TRIGGER_NAME)
drop_function(FUNCTION_NAME)
end
end

View file

@ -0,0 +1,41 @@
# frozen_string_literal: true
class AddUpdateVulnerabilityReadsLocationTrigger < Gitlab::Database::Migration[1.0]
include Gitlab::Database::SchemaHelpers
TRIGGER_NAME = 'trigger_update_location_on_vulnerability_occurrences_update'
FUNCTION_NAME = 'update_location_from_vulnerability_occurrences'
def up
create_trigger_function(FUNCTION_NAME, replace: true) do
<<~SQL
UPDATE
vulnerability_reads
SET
location_image = NEW.location->>'image',
cluster_agent_id = NEW.location->'kubernetes_resource'->>'agent_id'
WHERE
vulnerability_id = NEW.vulnerability_id;
RETURN NULL;
SQL
end
execute(<<~SQL)
CREATE TRIGGER #{TRIGGER_NAME}
AFTER UPDATE ON vulnerability_occurrences
FOR EACH ROW
WHEN (
NEW.report_type IN (2, 7) AND (
OLD.location->>'image' IS DISTINCT FROM NEW.location->>'image' OR
OLD.location->'kubernetes_resource'->>'agent_id' IS DISTINCT FROM NEW.location->'kubernetes_resource'->>'agent_id'
)
)
EXECUTE PROCEDURE #{FUNCTION_NAME}();
SQL
end
def down
drop_trigger(:vulnerability_occurrences, TRIGGER_NAME)
drop_function(FUNCTION_NAME)
end
end

View file

@ -0,0 +1,79 @@
# frozen_string_literal: true
class AddHasIssuesOnVulnerabilityReadsTrigger < Gitlab::Database::Migration[1.0]
include Gitlab::Database::SchemaHelpers
TRIGGER_ON_INSERT = 'trigger_update_has_issues_on_vulnerability_issue_links_update'
INSERT_FUNCTION_NAME = 'set_has_issues_on_vulnerability_reads'
TRIGGER_ON_DELETE = 'trigger_update_has_issues_on_vulnerability_issue_links_delete'
DELETE_FUNCTION_NAME = 'unset_has_issues_on_vulnerability_reads'
def up
create_trigger_function(INSERT_FUNCTION_NAME, replace: true) do
<<~SQL
UPDATE
vulnerability_reads
SET
has_issues = true
WHERE
vulnerability_id = NEW.vulnerability_id AND has_issues IS FALSE;
RETURN NULL;
SQL
end
execute(<<~SQL)
CREATE OR REPLACE FUNCTION #{DELETE_FUNCTION_NAME}()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
DECLARE
has_issue_links integer;
BEGIN
PERFORM 1
FROM
vulnerability_reads
WHERE
vulnerability_id = OLD.vulnerability_id
FOR UPDATE;
SELECT 1 INTO has_issue_links FROM vulnerability_issue_links WHERE vulnerability_id = OLD.vulnerability_id LIMIT 1;
IF (has_issue_links = 1) THEN
RETURN NULL;
END IF;
UPDATE
vulnerability_reads
SET
has_issues = false
WHERE
vulnerability_id = OLD.vulnerability_id;
RETURN NULL;
END
$$;
SQL
execute(<<~SQL)
CREATE TRIGGER #{TRIGGER_ON_INSERT}
AFTER INSERT ON vulnerability_issue_links
FOR EACH ROW
EXECUTE FUNCTION #{INSERT_FUNCTION_NAME}();
SQL
execute(<<~SQL)
CREATE TRIGGER #{TRIGGER_ON_DELETE}
AFTER DELETE ON vulnerability_issue_links
FOR EACH ROW
EXECUTE FUNCTION #{DELETE_FUNCTION_NAME}();
SQL
end
def down
drop_trigger(:vulnerability_issue_links, TRIGGER_ON_INSERT)
drop_function(INSERT_FUNCTION_NAME)
drop_trigger(:vulnerability_issue_links, TRIGGER_ON_DELETE)
drop_function(DELETE_FUNCTION_NAME)
end
end

View file

@ -0,0 +1 @@
c1af9546bdfa0f32c3c2faf362062cd300800514e5b1efd1fa8a1770753d00e5

View file

@ -0,0 +1 @@
8b51ae2b13066a56d2131efb7ea746335513031e751fb231e43121552d6f2b1d

View file

@ -0,0 +1 @@
f385631d0317630661d487011a228501a6cbc71de25ca457d75e6a815c598045

View file

@ -0,0 +1 @@
4726d84ff42e64b1c47c5ba454ff5be05f434a86bb2af4bbe27dd00e5e3da5cb

View file

@ -45,6 +45,39 @@ RETURN NULL;
END
$$;
CREATE FUNCTION insert_or_update_vulnerability_reads() RETURNS trigger
LANGUAGE plpgsql
AS $$
DECLARE
severity smallint;
state smallint;
report_type smallint;
resolved_on_default_branch boolean;
BEGIN
IF (NEW.vulnerability_id IS NULL AND (TG_OP = 'INSERT' OR TG_OP = 'UPDATE')) THEN
RETURN NULL;
END IF;
IF (TG_OP = 'UPDATE' AND OLD.vulnerability_id IS NOT NULL AND NEW.vulnerability_id IS NOT NULL) THEN
RETURN NULL;
END IF;
SELECT
vulnerabilities.severity, vulnerabilities.state, vulnerabilities.report_type, vulnerabilities.resolved_on_default_branch
INTO
severity, state, report_type, resolved_on_default_branch
FROM
vulnerabilities
WHERE
vulnerabilities.id = NEW.vulnerability_id;
INSERT INTO vulnerability_reads (vulnerability_id, project_id, scanner_id, report_type, severity, state, resolved_on_default_branch, uuid, location_image, cluster_agent_id)
VALUES (NEW.vulnerability_id, NEW.project_id, NEW.scanner_id, report_type, severity, state, resolved_on_default_branch, NEW.uuid::uuid, NEW.location->>'image', NEW.location->'kubernetes_resource'->>'agent_id')
ON CONFLICT(vulnerability_id) DO NOTHING;
RETURN NULL;
END
$$;
CREATE FUNCTION insert_projects_sync_event() RETURNS trigger
LANGUAGE plpgsql
AS $$
@ -107,6 +140,83 @@ RETURN NULL;
END
$$;
CREATE FUNCTION set_has_issues_on_vulnerability_reads() RETURNS trigger
LANGUAGE plpgsql
AS $$
BEGIN
UPDATE
vulnerability_reads
SET
has_issues = true
WHERE
vulnerability_id = NEW.vulnerability_id AND has_issues IS FALSE;
RETURN NULL;
END
$$;
CREATE FUNCTION unset_has_issues_on_vulnerability_reads() RETURNS trigger
LANGUAGE plpgsql
AS $$
DECLARE
has_issue_links integer;
BEGIN
PERFORM 1
FROM
vulnerability_reads
WHERE
vulnerability_id = OLD.vulnerability_id
FOR UPDATE;
SELECT 1 INTO has_issue_links FROM vulnerability_issue_links WHERE vulnerability_id = OLD.vulnerability_id LIMIT 1;
IF (has_issue_links = 1) THEN
RETURN NULL;
END IF;
UPDATE
vulnerability_reads
SET
has_issues = false
WHERE
vulnerability_id = OLD.vulnerability_id;
RETURN NULL;
END
$$;
CREATE FUNCTION update_location_from_vulnerability_occurrences() RETURNS trigger
LANGUAGE plpgsql
AS $$
BEGIN
UPDATE
vulnerability_reads
SET
location_image = NEW.location->>'image',
cluster_agent_id = NEW.location->'kubernetes_resource'->>'agent_id'
WHERE
vulnerability_id = NEW.vulnerability_id;
RETURN NULL;
END
$$;
CREATE FUNCTION update_vulnerability_reads_from_vulnerability() RETURNS trigger
LANGUAGE plpgsql
AS $$
BEGIN
UPDATE
vulnerability_reads
SET
severity = NEW.severity,
state = NEW.state,
resolved_on_default_branch = NEW.resolved_on_default_branch
WHERE vulnerability_id = NEW.id;
RETURN NULL;
END
$$;
CREATE TABLE audit_events (
id bigint NOT NULL,
author_id integer NOT NULL,
@ -29163,6 +29273,8 @@ CREATE TRIGGER trigger_has_external_wiki_on_type_new_updated AFTER UPDATE OF typ
CREATE TRIGGER trigger_has_external_wiki_on_update AFTER UPDATE ON integrations FOR EACH ROW WHEN (((new.type_new = 'Integrations::ExternalWiki'::text) AND (old.active <> new.active) AND (new.project_id IS NOT NULL))) EXECUTE FUNCTION set_has_external_wiki();
CREATE TRIGGER trigger_insert_or_update_vulnerability_reads_from_occurrences AFTER INSERT OR UPDATE ON vulnerability_occurrences FOR EACH ROW EXECUTE FUNCTION insert_or_update_vulnerability_reads();
CREATE TRIGGER trigger_namespaces_parent_id_on_insert AFTER INSERT ON namespaces FOR EACH ROW EXECUTE FUNCTION insert_namespaces_sync_event();
CREATE TRIGGER trigger_namespaces_parent_id_on_update AFTER UPDATE ON namespaces FOR EACH ROW WHEN ((old.parent_id IS DISTINCT FROM new.parent_id)) EXECUTE FUNCTION insert_namespaces_sync_event();
@ -29173,6 +29285,14 @@ CREATE TRIGGER trigger_projects_parent_id_on_update AFTER UPDATE ON projects FOR
CREATE TRIGGER trigger_type_new_on_insert AFTER INSERT ON integrations FOR EACH ROW EXECUTE FUNCTION integrations_set_type_new();
CREATE TRIGGER trigger_update_has_issues_on_vulnerability_issue_links_delete AFTER DELETE ON vulnerability_issue_links FOR EACH ROW EXECUTE FUNCTION unset_has_issues_on_vulnerability_reads();
CREATE TRIGGER trigger_update_has_issues_on_vulnerability_issue_links_update AFTER INSERT ON vulnerability_issue_links FOR EACH ROW EXECUTE FUNCTION set_has_issues_on_vulnerability_reads();
CREATE TRIGGER trigger_update_location_on_vulnerability_occurrences_update AFTER UPDATE ON vulnerability_occurrences FOR EACH ROW WHEN (((new.report_type = ANY (ARRAY[2, 7])) AND (((old.location ->> 'image'::text) IS DISTINCT FROM (new.location ->> 'image'::text)) OR (((old.location -> 'kubernetes_resource'::text) ->> 'agent_id'::text) IS DISTINCT FROM ((new.location -> 'kubernetes_resource'::text) ->> 'agent_id'::text))))) EXECUTE FUNCTION update_location_from_vulnerability_occurrences();
CREATE TRIGGER trigger_update_vulnerability_reads_on_vulnerability_update AFTER UPDATE ON vulnerabilities FOR EACH ROW WHEN (((old.severity IS DISTINCT FROM new.severity) OR (old.state IS DISTINCT FROM new.state) OR (old.resolved_on_default_branch IS DISTINCT FROM new.resolved_on_default_branch))) EXECUTE FUNCTION update_vulnerability_reads_from_vulnerability();
CREATE TRIGGER users_loose_fk_trigger AFTER DELETE ON users REFERENCING OLD TABLE AS old_table FOR EACH STATEMENT EXECUTE FUNCTION insert_into_loose_foreign_keys_deleted_records();
ALTER TABLE ONLY chat_names

View file

@ -721,6 +721,18 @@ The `merged_by` field in the [merge request API](https://docs.gitlab.com/ee/api/
## 14.8
### Querying Usage Trends via the `instanceStatisticsMeasurements` GraphQL node
WARNING:
This feature will be changed or removed in 15.0
as a [breaking change](https://docs.gitlab.com/ee/development/contributing/#breaking-changes).
Before updating GitLab, review the details carefully to determine if you need to make any
changes to your code, settings, or workflow.
The `instanceStatisticsMeasurements` GraphQL node has been renamed to `usageTrendsMeasurements` in 13.10 and the old field name has been marked as deprecated. To fix the existing GraphQL queries, replace `instanceStatisticsMeasurements` with `usageTrendsMeasurements`.
**Planned removal milestone: 15.0 (2022-05-22)**
### REST and GraphQL API Runner usage of `active` replaced by `paused`
WARNING:

View file

@ -297,16 +297,16 @@ To delete a project:
1. Select **Delete project**
1. Confirm this action by completing the field.
## Projects pending deletion **(PREMIUM SELF)**
## Projects pending deletion **(PREMIUM)**
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/37014) in GitLab 13.3 for Administrators.
> - [Tab renamed](https://gitlab.com/gitlab-org/gitlab/-/issues/347468) from **Deleted projects** in GitLab 14.6.
> - [Available to all users](https://gitlab.com/gitlab-org/gitlab/-/issues/346976) in GitLab 14.8 [with a flag](../../administration/feature_flags.md) named `project_owners_list_project_pending_deletion`. Disabled by default.
> - [Available to all users](https://gitlab.com/gitlab-org/gitlab/-/issues/346976) in GitLab 14.8 [with a flag](../../administration/feature_flags.md) named `project_owners_list_project_pending_deletion`. Enabled by default.
FLAG:
On self-managed GitLab, by default this feature is available to administrators only. To make it available to all users,
ask an administrator to [enable the feature flag](../../administration/feature_flags.md) named `project_owners_list_project_pending_deletion`.
On GitLab.com, this feature is available to GitLab.com administrators only. The feature being used by all users is not ready for production use.
On self-managed GitLab, by default this feature is available to all users. To make it available for administrators only,
ask an administrator to [disable the feature flag](../../administration/feature_flags.md) named `project_owners_list_project_pending_deletion`.
On GitLab.com, this feature is available to all users.
When delayed project deletion is [enabled for a group](../group/index.md#enable-delayed-project-deletion),
projects within that group are not deleted immediately, but only after a delay. To access a list of all projects that are pending deletion:

View file

@ -132,6 +132,29 @@ RSpec.describe GroupsController, factory_default: :keep do
end
end
end
describe 'require_verification_for_namespace_creation experiment', :experiment do
before do
sign_in(owner)
stub_experiments(require_verification_for_namespace_creation: :candidate)
end
it 'tracks a "start_create_group" event' do
expect(experiment(:require_verification_for_namespace_creation)).to track(
:start_create_group
).on_next_instance.with_context(user: owner)
get :new
end
context 'when creating a sub-group' do
it 'does not track a "start_create_group" event' do
expect(experiment(:require_verification_for_namespace_creation)).not_to track(:start_create_group)
get :new, params: { parent_id: group.id }
end
end
end
end
describe 'GET #activity' do

View file

@ -5,7 +5,8 @@ require 'spec_helper'
RSpec.describe RequireVerificationForNamespaceCreationExperiment, :experiment do
subject(:experiment) { described_class.new(user: user) }
let_it_be(:user) { create(:user) }
let(:user_created_at) { RequireVerificationForNamespaceCreationExperiment::EXPERIMENT_START_DATE + 1.hour }
let(:user) { create(:user, created_at: user_created_at) }
describe '#candidate?' do
context 'when experiment subject is candidate' do
@ -56,4 +57,21 @@ RSpec.describe RequireVerificationForNamespaceCreationExperiment, :experiment do
end
end
end
describe 'exclusions' do
context 'when user is new' do
it 'is not excluded' do
expect(subject).not_to exclude(user: user)
end
end
context 'when user is NOT new' do
let(:user_created_at) { RequireVerificationForNamespaceCreationExperiment::EXPERIMENT_START_DATE - 1.day }
let(:user) { create(:user, created_at: user_created_at) }
it 'is excluded' do
expect(subject).to exclude(user: user)
end
end
end
end

View file

@ -3,6 +3,7 @@
exports[`packages_list_app renders 1`] = `
<div>
<infrastructure-title-stub
count="1"
helpurl="foo"
/>

View file

@ -10,7 +10,9 @@ describe('Infrastructure Title', () => {
const findTitleArea = () => wrapper.find(TitleArea);
const findMetadataItem = () => wrapper.find(MetadataItem);
const mountComponent = (propsData = { helpUrl: 'foo' }) => {
const exampleProps = { helpUrl: 'http://example.gitlab.com/help' };
const mountComponent = (propsData = exampleProps) => {
wrapper = shallowMount(component, {
store,
propsData,
@ -26,23 +28,36 @@ describe('Infrastructure Title', () => {
});
describe('title area', () => {
it('exists', () => {
beforeEach(() => {
mountComponent();
});
it('exists', () => {
expect(findTitleArea().exists()).toBe(true);
});
it('has the correct props', () => {
mountComponent();
it('has the correct title', () => {
expect(findTitleArea().props('title')).toBe('Infrastructure Registry');
});
expect(findTitleArea().props()).toMatchObject({
title: 'Infrastructure Registry',
infoMessages: [
describe('with no modules', () => {
it('has no info message', () => {
expect(findTitleArea().props('infoMessages')).toStrictEqual([]);
});
});
describe('with at least one module', () => {
beforeEach(() => {
mountComponent({ ...exampleProps, count: 1 });
});
it('has an info message', () => {
expect(findTitleArea().props('infoMessages')).toStrictEqual([
{
text: 'Publish and share your modules. %{docLinkStart}More information%{docLinkEnd}',
link: 'foo',
link: exampleProps.helpUrl,
},
],
]);
});
});
});
@ -51,15 +66,15 @@ describe('Infrastructure Title', () => {
count | exist | text
${null} | ${false} | ${''}
${undefined} | ${false} | ${''}
${0} | ${true} | ${'0 Modules'}
${0} | ${false} | ${''}
${1} | ${true} | ${'1 Module'}
${2} | ${true} | ${'2 Modules'}
`('when count is $count metadata item', ({ count, exist, text }) => {
beforeEach(() => {
mountComponent({ count, helpUrl: 'foo' });
mountComponent({ ...exampleProps, count });
});
it(`is ${exist} that it exists`, () => {
it(exist ? 'exists' : 'does not exist', () => {
expect(findMetadataItem().exists()).toBe(exist);
});

View file

@ -35,7 +35,7 @@ describe('packages_list_app', () => {
const findListComponent = () => wrapper.find(PackageList);
const findInfrastructureSearch = () => wrapper.find(InfrastructureSearch);
const createStore = (filter = []) => {
const createStore = ({ filter = [], packageCount = 0 } = {}) => {
store = new Vuex.Store({
state: {
isLoading: false,
@ -46,6 +46,9 @@ describe('packages_list_app', () => {
packageHelpUrl: 'foo',
},
filter,
pagination: {
total: packageCount,
},
},
});
store.dispatch = jest.fn();
@ -68,6 +71,7 @@ describe('packages_list_app', () => {
beforeEach(() => {
createStore();
jest.spyOn(packageUtils, 'getQueryParams').mockReturnValue({});
mountComponent();
});
afterEach(() => {
@ -75,30 +79,26 @@ describe('packages_list_app', () => {
});
it('renders', () => {
createStore({ packageCount: 1 });
mountComponent();
expect(wrapper.element).toMatchSnapshot();
});
it('call requestPackagesList on page:changed', () => {
mountComponent();
store.dispatch.mockClear();
it('calls requestPackagesList on page:changed', () => {
const list = findListComponent();
list.vm.$emit('page:changed', 1);
expect(store.dispatch).toHaveBeenCalledWith('requestPackagesList', { page: 1 });
});
it('call requestDeletePackage on package:delete', () => {
mountComponent();
it('calls requestDeletePackage on package:delete', () => {
const list = findListComponent();
list.vm.$emit('package:delete', 'foo');
expect(store.dispatch).toHaveBeenCalledWith('requestDeletePackage', 'foo');
});
it('does call requestPackagesList only one time on render', () => {
mountComponent();
it('calls requestPackagesList only once on render', () => {
expect(store.dispatch).toHaveBeenCalledTimes(3);
expect(store.dispatch).toHaveBeenNthCalledWith(1, 'setSorting', expect.any(Object));
expect(store.dispatch).toHaveBeenNthCalledWith(2, 'setFilter', expect.any(Array));
@ -113,9 +113,12 @@ describe('packages_list_app', () => {
orderBy: 'created',
};
it('calls setSorting with the query string based sorting', () => {
beforeEach(() => {
createStore();
jest.spyOn(packageUtils, 'getQueryParams').mockReturnValue(defaultQueryParamsMock);
});
it('calls setSorting with the query string based sorting', () => {
mountComponent();
expect(store.dispatch).toHaveBeenNthCalledWith(1, 'setSorting', {
@ -125,8 +128,6 @@ describe('packages_list_app', () => {
});
it('calls setFilter with the query string based filters', () => {
jest.spyOn(packageUtils, 'getQueryParams').mockReturnValue(defaultQueryParamsMock);
mountComponent();
expect(store.dispatch).toHaveBeenNthCalledWith(2, 'setFilter', [
@ -150,8 +151,6 @@ describe('packages_list_app', () => {
describe('empty state', () => {
it('generate the correct empty list link', () => {
mountComponent();
const link = findListComponent().find(GlLink);
expect(link.attributes('href')).toBe(emptyListHelpUrl);
@ -159,8 +158,6 @@ describe('packages_list_app', () => {
});
it('includes the right content on the default tab', () => {
mountComponent();
const heading = findEmptyState().find('h1');
expect(heading.text()).toBe('There are no packages yet');
@ -169,7 +166,7 @@ describe('packages_list_app', () => {
describe('filter without results', () => {
beforeEach(() => {
createStore([{ type: 'something' }]);
createStore({ filter: [{ type: 'something' }] });
mountComponent();
});
@ -181,20 +178,30 @@ describe('packages_list_app', () => {
});
});
describe('Search', () => {
it('exists', () => {
mountComponent();
expect(findInfrastructureSearch().exists()).toBe(true);
describe('search', () => {
describe('with no packages', () => {
it('does not exist', () => {
expect(findInfrastructureSearch().exists()).toBe(false);
});
});
it('on update fetches data from the store', () => {
mountComponent();
store.dispatch.mockClear();
describe('with packages', () => {
beforeEach(() => {
createStore({ packageCount: 1 });
mountComponent();
});
findInfrastructureSearch().vm.$emit('update');
it('exists', () => {
expect(findInfrastructureSearch().exists()).toBe(true);
});
expect(store.dispatch).toHaveBeenCalledWith('requestPackagesList');
it('on update fetches data from the store', () => {
store.dispatch.mockClear();
findInfrastructureSearch().vm.$emit('update');
expect(store.dispatch).toHaveBeenCalledWith('requestPackagesList');
});
});
});

View file

@ -87,7 +87,7 @@ RSpec.describe Gitlab::BackgroundMigration::RemoveDuplicateVulnerabilitiesFindin
let!(:unrelated_finding) do
create_finding!(
id: 9999999,
uuid: "unreleated_finding",
uuid: Gitlab::UUID.v5(SecureRandom.hex),
vulnerability_id: nil,
report_type: 1,
location_fingerprint: 'random_location_fingerprint',

View file

@ -0,0 +1,151 @@
# frozen_string_literal: true
require 'spec_helper'
require_migration!
RSpec.describe AddInsertOrUpdateVulnerabilityReadsTrigger do
let(:migration) { described_class.new }
let(:vulnerabilities) { table(:vulnerabilities) }
let(:vulnerability_reads) { table(:vulnerability_reads) }
let(:vulnerabilities_findings) { table(:vulnerability_occurrences) }
let(:namespace) { table(:namespaces).create!(name: 'user', path: 'user') }
let(:user) { table(:users).create!(id: 13, email: 'author@example.com', username: 'author', projects_limit: 10) }
let(:project) { table(:projects).create!(id: 123, namespace_id: namespace.id) }
let(:scanner) { table(:vulnerability_scanners).create!(project_id: project.id, external_id: 'test 1', name: 'test scanner 1') }
let(:vulnerability) do
create_vulnerability!(
project_id: project.id,
author_id: user.id
)
end
let(:vulnerability2) do
create_vulnerability!(
project_id: project.id,
author_id: user.id
)
end
let(:identifier) do
table(:vulnerability_identifiers).create!(
project_id: project.id,
external_type: 'uuid-v5',
external_id: 'uuid-v5',
fingerprint: '7e394d1b1eb461a7406d7b1e08f057a1cf11287a',
name: 'Identifier for UUIDv5')
end
let(:finding) do
create_finding!(
project_id: project.id,
scanner_id: scanner.id,
primary_identifier_id: identifier.id
)
end
describe '#up' do
before do
migrate!
end
describe 'UPDATE trigger' do
context 'when vulnerability_id is updated' do
it 'creates a new vulnerability_reads row' do
expect do
finding.update!(vulnerability_id: vulnerability.id)
end.to change { vulnerability_reads.count }.from(0).to(1)
end
end
context 'when vulnerability_id is not updated' do
it 'does not create a new vulnerability_reads row' do
finding.update!(vulnerability_id: nil)
expect do
finding.update!(location: '')
end.not_to change { vulnerability_reads.count }
end
end
end
describe 'INSERT trigger' do
context 'when vulnerability_id is set' do
it 'creates a new vulnerability_reads row' do
expect do
create_finding!(
vulnerability_id: vulnerability2.id,
project_id: project.id,
scanner_id: scanner.id,
primary_identifier_id: identifier.id
)
end.to change { vulnerability_reads.count }.from(0).to(1)
end
end
context 'when vulnerability_id is not set' do
it 'does not create a new vulnerability_reads row' do
expect do
create_finding!(
project_id: project.id,
scanner_id: scanner.id,
primary_identifier_id: identifier.id
)
end.not_to change { vulnerability_reads.count }
end
end
end
end
describe '#down' do
before do
migration.up
migration.down
end
it 'drops the trigger' do
expect do
finding.update!(vulnerability_id: vulnerability.id)
end.not_to change { vulnerability_reads.count }
end
end
private
def create_vulnerability!(project_id:, author_id:, title: 'test', severity: 7, confidence: 7, report_type: 0)
vulnerabilities.create!(
project_id: project_id,
author_id: author_id,
title: title,
severity: severity,
confidence: confidence,
report_type: report_type
)
end
# rubocop:disable Metrics/ParameterLists
def create_finding!(
vulnerability_id: nil, project_id:, scanner_id:, primary_identifier_id:,
name: "test", severity: 7, confidence: 7, report_type: 0,
project_fingerprint: '123qweasdzxc', location: { "image" => "alpine:3.4" }, location_fingerprint: 'test',
metadata_version: 'test', raw_metadata: 'test', uuid: SecureRandom.uuid)
vulnerabilities_findings.create!(
vulnerability_id: vulnerability_id,
project_id: project_id,
name: name,
severity: severity,
confidence: confidence,
report_type: report_type,
project_fingerprint: project_fingerprint,
scanner_id: scanner_id,
primary_identifier_id: primary_identifier_id,
location: location,
location_fingerprint: location_fingerprint,
metadata_version: metadata_version,
raw_metadata: raw_metadata,
uuid: uuid
)
end
# rubocop:enable Metrics/ParameterLists
end

View file

@ -0,0 +1,128 @@
# frozen_string_literal: true
require 'spec_helper'
require_migration!
RSpec.describe AddUpdateVulnerabilityReadsTrigger do
let(:migration) { described_class.new }
let(:vulnerability_reads) { table(:vulnerability_reads) }
let(:issue_links) { table(:vulnerability_issue_links) }
let(:vulnerabilities) { table(:vulnerabilities) }
let(:vulnerabilities_findings) { table(:vulnerability_occurrences) }
let(:namespace) { table(:namespaces).create!(name: 'user', path: 'user') }
let(:user) { table(:users).create!(id: 13, email: 'author@example.com', username: 'author', projects_limit: 10) }
let(:project) { table(:projects).create!(id: 123, namespace_id: namespace.id) }
let(:issue) { table(:issues).create!(description: '1234', state_id: 1, project_id: project.id) }
let(:scanner) { table(:vulnerability_scanners).create!(project_id: project.id, external_id: 'test 1', name: 'test scanner 1') }
let(:vulnerability) do
create_vulnerability!(
project_id: project.id,
report_type: 7,
author_id: user.id
)
end
let(:identifier) do
table(:vulnerability_identifiers).create!(
project_id: project.id,
external_type: 'uuid-v5',
external_id: 'uuid-v5',
fingerprint: '7e394d1b1eb461a7406d7b1e08f057a1cf11287a',
name: 'Identifier for UUIDv5')
end
describe '#up' do
before do
migrate!
end
describe 'UPDATE trigger' do
before do
create_finding!(
vulnerability_id: vulnerability.id,
project_id: project.id,
scanner_id: scanner.id,
report_type: 7,
primary_identifier_id: identifier.id
)
end
context 'when vulnerability attributes are updated' do
it 'updates vulnerability attributes in vulnerability_reads' do
expect do
vulnerability.update!(severity: 6)
end.to change { vulnerability_reads.first.severity }.from(7).to(6)
end
end
context 'when vulnerability attributes are not updated' do
it 'does not update vulnerability attributes in vulnerability_reads' do
expect do
vulnerability.update!(title: "New vulnerability")
end.not_to change { vulnerability_reads.first }
end
end
end
end
describe '#down' do
before do
migration.up
migration.down
create_finding!(
vulnerability_id: vulnerability.id,
project_id: project.id,
scanner_id: scanner.id,
report_type: 7,
primary_identifier_id: identifier.id
)
end
it 'drops the trigger' do
expect do
vulnerability.update!(severity: 6)
end.not_to change { vulnerability_reads.first.severity }
end
end
private
def create_vulnerability!(project_id:, author_id:, title: 'test', severity: 7, confidence: 7, report_type: 0)
vulnerabilities.create!(
project_id: project_id,
author_id: author_id,
title: title,
severity: severity,
confidence: confidence,
report_type: report_type
)
end
# rubocop:disable Metrics/ParameterLists
def create_finding!(
vulnerability_id: nil, project_id:, scanner_id:, primary_identifier_id:,
name: "test", severity: 7, confidence: 7, report_type: 0,
project_fingerprint: '123qweasdzxc', location: { "image" => "alpine:3.4" }, location_fingerprint: 'test',
metadata_version: 'test', raw_metadata: 'test', uuid: SecureRandom.uuid)
vulnerabilities_findings.create!(
vulnerability_id: vulnerability_id,
project_id: project_id,
name: name,
severity: severity,
confidence: confidence,
report_type: report_type,
project_fingerprint: project_fingerprint,
scanner_id: scanner_id,
primary_identifier_id: primary_identifier_id,
location: location,
location_fingerprint: location_fingerprint,
metadata_version: metadata_version,
raw_metadata: raw_metadata,
uuid: uuid
)
end
# rubocop:enable Metrics/ParameterLists
end

View file

@ -0,0 +1,136 @@
# frozen_string_literal: true
require 'spec_helper'
require_migration!
RSpec.describe AddUpdateVulnerabilityReadsLocationTrigger do
let(:migration) { described_class.new }
let(:vulnerability_reads) { table(:vulnerability_reads) }
let(:issue_links) { table(:vulnerability_issue_links) }
let(:vulnerabilities) { table(:vulnerabilities) }
let(:vulnerabilities_findings) { table(:vulnerability_occurrences) }
let(:namespace) { table(:namespaces).create!(name: 'user', path: 'user') }
let(:user) { table(:users).create!(id: 13, email: 'author@example.com', username: 'author', projects_limit: 10) }
let(:project) { table(:projects).create!(id: 123, namespace_id: namespace.id) }
let(:issue) { table(:issues).create!(description: '1234', state_id: 1, project_id: project.id) }
let(:scanner) { table(:vulnerability_scanners).create!(project_id: project.id, external_id: 'test 1', name: 'test scanner 1') }
let(:vulnerability) do
create_vulnerability!(
project_id: project.id,
report_type: 7,
author_id: user.id
)
end
let(:identifier) do
table(:vulnerability_identifiers).create!(
project_id: project.id,
external_type: 'uuid-v5',
external_id: 'uuid-v5',
fingerprint: '7e394d1b1eb461a7406d7b1e08f057a1cf11287a',
name: 'Identifier for UUIDv5')
end
describe '#up' do
before do
migrate!
end
describe 'UPDATE trigger' do
context 'when image is updated' do
it 'updates location_image in vulnerability_reads' do
finding = create_finding!(
vulnerability_id: vulnerability.id,
project_id: project.id,
scanner_id: scanner.id,
report_type: 7,
location: { "image" => "alpine:3.4" },
primary_identifier_id: identifier.id
)
expect do
finding.update!(location: { "image" => "alpine:4", "kubernetes_resource" => { "agent_id" => "1234" } })
end.to change { vulnerability_reads.first.location_image }.from("alpine:3.4").to("alpine:4")
end
end
context 'when image is not updated' do
it 'updates location_image in vulnerability_reads' do
finding = create_finding!(
vulnerability_id: vulnerability.id,
project_id: project.id,
scanner_id: scanner.id,
report_type: 7,
location: { "image" => "alpine:3.4", "kubernetes_resource" => { "agent_id" => "1234" } },
primary_identifier_id: identifier.id
)
expect do
finding.update!(project_fingerprint: "123qweasdzx")
end.not_to change { vulnerability_reads.first.location_image }
end
end
end
end
describe '#down' do
before do
migration.up
migration.down
end
it 'drops the trigger' do
finding = create_finding!(
vulnerability_id: vulnerability.id,
project_id: project.id,
scanner_id: scanner.id,
primary_identifier_id: identifier.id
)
expect do
finding.update!(location: '{"image":"alpine:4"}')
end.not_to change { vulnerability_reads.first.location_image }
end
end
private
def create_vulnerability!(project_id:, author_id:, title: 'test', severity: 7, confidence: 7, report_type: 0)
vulnerabilities.create!(
project_id: project_id,
author_id: author_id,
title: title,
severity: severity,
confidence: confidence,
report_type: report_type
)
end
# rubocop:disable Metrics/ParameterLists
def create_finding!(
vulnerability_id: nil, project_id:, scanner_id:, primary_identifier_id:,
name: "test", severity: 7, confidence: 7, report_type: 0,
project_fingerprint: '123qweasdzxc', location: { "image" => "alpine:3.4" }, location_fingerprint: 'test',
metadata_version: 'test', raw_metadata: 'test', uuid: SecureRandom.uuid)
vulnerabilities_findings.create!(
vulnerability_id: vulnerability_id,
project_id: project_id,
name: name,
severity: severity,
confidence: confidence,
report_type: report_type,
project_fingerprint: project_fingerprint,
scanner_id: scanner_id,
primary_identifier_id: primary_identifier_id,
location: location,
location_fingerprint: location_fingerprint,
metadata_version: metadata_version,
raw_metadata: raw_metadata,
uuid: uuid
)
end
# rubocop:enable Metrics/ParameterLists
end

View file

@ -0,0 +1,134 @@
# frozen_string_literal: true
require 'spec_helper'
require_migration!
RSpec.describe AddHasIssuesOnVulnerabilityReadsTrigger do
let(:migration) { described_class.new }
let(:vulnerability_reads) { table(:vulnerability_reads) }
let(:issue_links) { table(:vulnerability_issue_links) }
let(:vulnerabilities) { table(:vulnerabilities) }
let(:vulnerabilities_findings) { table(:vulnerability_occurrences) }
let(:namespace) { table(:namespaces).create!(name: 'user', path: 'user') }
let(:user) { table(:users).create!(id: 13, email: 'author@example.com', username: 'author', projects_limit: 10) }
let(:project) { table(:projects).create!(id: 123, namespace_id: namespace.id) }
let(:issue) { table(:issues).create!(description: '1234', state_id: 1, project_id: project.id) }
let(:scanner) { table(:vulnerability_scanners).create!(project_id: project.id, external_id: 'test 1', name: 'test scanner 1') }
let(:vulnerability) do
create_vulnerability!(
project_id: project.id,
author_id: user.id
)
end
let(:identifier) do
table(:vulnerability_identifiers).create!(
project_id: project.id,
external_type: 'uuid-v5',
external_id: 'uuid-v5',
fingerprint: '7e394d1b1eb461a7406d7b1e08f057a1cf11287a',
name: 'Identifier for UUIDv5')
end
before do
create_finding!(
vulnerability_id: vulnerability.id,
project_id: project.id,
scanner_id: scanner.id,
primary_identifier_id: identifier.id
)
@vulnerability_read = vulnerability_reads.first
end
describe '#up' do
before do
migrate!
end
describe 'INSERT trigger' do
it 'updates has_issues in vulnerability_reads' do
expect do
issue_links.create!(vulnerability_id: vulnerability.id, issue_id: issue.id)
end.to change { @vulnerability_read.reload.has_issues }.from(false).to(true)
end
end
describe 'DELETE trigger' do
let(:issue2) { table(:issues).create!(description: '1234', state_id: 1, project_id: project.id) }
it 'does not change has_issues when there exists another issue' do
issue_link1 = issue_links.create!(vulnerability_id: vulnerability.id, issue_id: issue.id)
issue_links.create!(vulnerability_id: vulnerability.id, issue_id: issue2.id)
expect do
issue_link1.delete
end.not_to change { @vulnerability_read.reload.has_issues }
end
it 'unsets has_issues when all issues are deleted' do
issue_link1 = issue_links.create!(vulnerability_id: vulnerability.id, issue_id: issue.id)
issue_link2 = issue_links.create!(vulnerability_id: vulnerability.id, issue_id: issue2.id)
expect do
issue_link1.delete
issue_link2.delete
end.to change { @vulnerability_read.reload.has_issues }.from(true).to(false)
end
end
end
describe '#down' do
before do
migration.up
migration.down
end
it 'drops the trigger' do
expect do
issue_links.create!(vulnerability_id: vulnerability.id, issue_id: issue.id)
end.not_to change { @vulnerability_read.has_issues }
end
end
private
def create_vulnerability!(project_id:, author_id:, title: 'test', severity: 7, confidence: 7, report_type: 0)
vulnerabilities.create!(
project_id: project_id,
author_id: author_id,
title: title,
severity: severity,
confidence: confidence,
report_type: report_type
)
end
# rubocop:disable Metrics/ParameterLists
def create_finding!(
vulnerability_id: nil, project_id:, scanner_id:, primary_identifier_id:,
name: "test", severity: 7, confidence: 7, report_type: 0,
project_fingerprint: '123qweasdzxc', location: { "image" => "alpine:3.4" }, location_fingerprint: 'test',
metadata_version: 'test', raw_metadata: 'test', uuid: SecureRandom.uuid)
vulnerabilities_findings.create!(
vulnerability_id: vulnerability_id,
project_id: project_id,
name: name,
severity: severity,
confidence: confidence,
report_type: report_type,
project_fingerprint: project_fingerprint,
scanner_id: scanner_id,
primary_identifier_id: primary_identifier_id,
location: location,
location_fingerprint: location_fingerprint,
metadata_version: metadata_version,
raw_metadata: raw_metadata,
uuid: uuid
)
end
# rubocop:enable Metrics/ParameterLists
end