Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
1edfc04d9f
commit
8bdb37de3a
34 changed files with 1062 additions and 71 deletions
|
@ -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
|
||||
|
|
|
@ -1 +1 @@
|
|||
13.23.0
|
||||
13.23.1
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -4,6 +4,7 @@ class Projects::ServiceDeskController < Projects::ApplicationController
|
|||
before_action :authorize_admin_project!
|
||||
|
||||
feature_category :service_desk
|
||||
urgency :low
|
||||
|
||||
def show
|
||||
json_response
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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' }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
1
db/schema_migrations/20220106111958
Normal file
1
db/schema_migrations/20220106111958
Normal file
|
@ -0,0 +1 @@
|
|||
c1af9546bdfa0f32c3c2faf362062cd300800514e5b1efd1fa8a1770753d00e5
|
1
db/schema_migrations/20220106112043
Normal file
1
db/schema_migrations/20220106112043
Normal file
|
@ -0,0 +1 @@
|
|||
8b51ae2b13066a56d2131efb7ea746335513031e751fb231e43121552d6f2b1d
|
1
db/schema_migrations/20220106112085
Normal file
1
db/schema_migrations/20220106112085
Normal file
|
@ -0,0 +1 @@
|
|||
f385631d0317630661d487011a228501a6cbc71de25ca457d75e6a815c598045
|
1
db/schema_migrations/20220106163326
Normal file
1
db/schema_migrations/20220106163326
Normal file
|
@ -0,0 +1 @@
|
|||
4726d84ff42e64b1c47c5ba454ff5be05f434a86bb2af4bbe27dd00e5e3da5cb
|
120
db/structure.sql
120
db/structure.sql
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
exports[`packages_list_app renders 1`] = `
|
||||
<div>
|
||||
<infrastructure-title-stub
|
||||
count="1"
|
||||
helpurl="foo"
|
||||
/>
|
||||
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
Loading…
Reference in a new issue