Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
102640e087
commit
b8fbfa9c70
|
|
@ -1278,7 +1278,6 @@ Layout/ArgumentAlignment:
|
|||
- 'ee/spec/services/vulnerabilities/user_notes_count_service_spec.rb'
|
||||
- 'ee/spec/services/vulnerability_feedback/create_service_spec.rb'
|
||||
- 'ee/spec/services/vulnerability_merge_request_links/create_service_spec.rb'
|
||||
- 'ee/spec/tasks/gitlab/elastic_rake_spec.rb'
|
||||
- 'lib/api/access_requests.rb'
|
||||
- 'lib/api/admin/plan_limits.rb'
|
||||
- 'lib/api/alert_management_alerts.rb'
|
||||
|
|
|
|||
|
|
@ -1305,7 +1305,6 @@ Layout/LineLength:
|
|||
- 'ee/lib/system_check/geo/geo_database_configured_check.rb'
|
||||
- 'ee/lib/tasks/geo.rake'
|
||||
- 'ee/lib/tasks/geo/git.rake'
|
||||
- 'ee/lib/tasks/gitlab/elastic.rake'
|
||||
- 'ee/lib/tasks/gitlab/elastic/test.rake'
|
||||
- 'ee/lib/tasks/gitlab/seed/metrics.rake'
|
||||
- 'ee/lib/world.rb'
|
||||
|
|
|
|||
|
|
@ -115,7 +115,6 @@ Lint/UnusedBlockArgument:
|
|||
- 'ee/lib/gitlab/insights/reducers/count_per_label_reducer.rb'
|
||||
- 'ee/lib/gitlab/proxy.rb'
|
||||
- 'ee/lib/tasks/contracts/merge_requests.rake'
|
||||
- 'ee/lib/tasks/gitlab/elastic.rake'
|
||||
- 'ee/lib/tasks/gitlab/indexer.rake'
|
||||
- 'ee/lib/tasks/gitlab/seed/insights.rake'
|
||||
- 'ee/spec/elastic/migrate/20220118150500_delete_orphaned_commits_spec.rb'
|
||||
|
|
|
|||
|
|
@ -316,7 +316,6 @@ Lint/UnusedMethodArgument:
|
|||
- 'ee/lib/gitlab/package_metadata/connector/gcp.rb'
|
||||
- 'ee/lib/gitlab/package_metadata/connector/offline.rb'
|
||||
- 'ee/lib/gitlab/zoekt/search_results.rb'
|
||||
- 'ee/lib/tasks/gitlab/elastic.rake'
|
||||
- 'ee/spec/features/boards/swimlanes/epics_swimlanes_filtering_spec.rb'
|
||||
- 'ee/spec/features/groups/group_roadmap_spec.rb'
|
||||
- 'ee/spec/graphql/ee/resolvers/namespace_projects_resolver_spec.rb'
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ Naming/HeredocDelimiterNaming:
|
|||
- 'ee/spec/services/security/security_orchestration_policies/create_pipeline_service_spec.rb'
|
||||
- 'ee/spec/services/security/security_orchestration_policies/policy_commit_service_spec.rb'
|
||||
- 'ee/spec/support/helpers/ee/ldap_helpers.rb'
|
||||
- 'ee/spec/tasks/gitlab/elastic_rake_spec.rb'
|
||||
- 'lib/api/metadata.rb'
|
||||
- 'lib/backup/helper.rb'
|
||||
- 'lib/feature/shared.rb'
|
||||
|
|
|
|||
|
|
@ -872,7 +872,6 @@ RSpec/ContextWording:
|
|||
- 'ee/spec/support/shared_examples/services/search_notes_shared_examples.rb'
|
||||
- 'ee/spec/support/shared_examples/services/search_service_shared_examples.rb'
|
||||
- 'ee/spec/support/shared_examples/services/update_issuable_health_status_shared_examples.rb'
|
||||
- 'ee/spec/tasks/gitlab/elastic_rake_spec.rb'
|
||||
- 'ee/spec/tasks/gitlab/license_rake_spec.rb'
|
||||
- 'ee/spec/views/admin/application_settings/_elasticsearch_form.html.haml_spec.rb'
|
||||
- 'ee/spec/views/admin/users/show.html.haml_spec.rb'
|
||||
|
|
|
|||
|
|
@ -93,6 +93,9 @@ Search/NamespacedClass:
|
|||
- 'ee/lib/elastic/latest/config.rb'
|
||||
- 'ee/lib/elastic/latest/custom_language_analyzers.rb'
|
||||
- 'ee/lib/elastic/latest/document_should_be_deleted_from_index_error.rb'
|
||||
- 'ee/lib/elastic/latest/epic_class_proxy.rb'
|
||||
- 'ee/lib/elastic/latest/epic_config.rb'
|
||||
- 'ee/lib/elastic/latest/epic_instance_proxy.rb'
|
||||
- 'ee/lib/elastic/latest/git_class_proxy.rb'
|
||||
- 'ee/lib/elastic/latest/git_instance_proxy.rb'
|
||||
- 'ee/lib/elastic/latest/issue_class_proxy.rb'
|
||||
|
|
@ -131,6 +134,8 @@ Search/NamespacedClass:
|
|||
- 'ee/lib/elastic/v12p1/application_class_proxy.rb'
|
||||
- 'ee/lib/elastic/v12p1/application_instance_proxy.rb'
|
||||
- 'ee/lib/elastic/v12p1/config.rb'
|
||||
- 'ee/lib/elastic/v12p1/epic_class_proxy.rb'
|
||||
- 'ee/lib/elastic/v12p1/epic_instance_proxy.rb'
|
||||
- 'ee/lib/elastic/v12p1/issue_class_proxy.rb'
|
||||
- 'ee/lib/elastic/v12p1/issue_instance_proxy.rb'
|
||||
- 'ee/lib/elastic/v12p1/merge_request_class_proxy.rb'
|
||||
|
|
|
|||
|
|
@ -237,7 +237,6 @@ Style/FormatString:
|
|||
- 'ee/lib/gitlab/licenses/submit_license_usage_data_banner.rb'
|
||||
- 'ee/lib/gitlab/manual_quarterly_co_term_banner.rb'
|
||||
- 'ee/lib/gitlab/vulnerabilities/container_scanning_vulnerability.rb'
|
||||
- 'ee/lib/tasks/gitlab/elastic.rake'
|
||||
- 'ee/spec/controllers/admin/licenses_controller_spec.rb'
|
||||
- 'ee/spec/controllers/groups/security/policies_controller_spec.rb'
|
||||
- 'ee/spec/features/admin/admin_settings_spec.rb'
|
||||
|
|
|
|||
|
|
@ -54,7 +54,6 @@ Style/StringConcatenation:
|
|||
- 'ee/lib/gitlab/elastic/search_results.rb'
|
||||
- 'ee/lib/gitlab/geo/git_ssh_proxy.rb'
|
||||
- 'ee/lib/omni_auth/strategies/kerberos.rb'
|
||||
- 'ee/lib/tasks/gitlab/elastic.rake'
|
||||
- 'ee/lib/tasks/gitlab/license.rake'
|
||||
- 'ee/spec/features/boards/boards_spec.rb'
|
||||
- 'ee/spec/features/projects/pipelines/pipeline_spec.rb'
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ Style/StringLiteralsInInterpolation:
|
|||
- 'ee/app/models/license.rb'
|
||||
- 'ee/app/services/epics/tree_reorder_service.rb'
|
||||
- 'ee/lib/ee/api/helpers/issues_helpers.rb'
|
||||
- 'ee/lib/tasks/gitlab/elastic.rake'
|
||||
- 'ee/spec/features/admin/admin_settings_spec.rb'
|
||||
- 'ee/spec/features/subscriptions/expiring_subscription_message_spec.rb'
|
||||
- 'ee/spec/lib/gitlab/expiring_subscription_message_spec.rb'
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
v16.1.3
|
||||
v16.2.0-rc1
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import { __, s__, sprintf, formatNumber } from '~/locale';
|
|||
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
||||
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
|
||||
import CiBadgeLink from '~/vue_shared/components/ci_badge_link.vue';
|
||||
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
|
||||
import SafeHtml from '~/vue_shared/directives/safe_html';
|
||||
import {
|
||||
LOAD_FAILURE,
|
||||
|
|
@ -30,7 +31,6 @@ import cancelPipelineMutation from '../graphql/mutations/cancel_pipeline.mutatio
|
|||
import deletePipelineMutation from '../graphql/mutations/delete_pipeline.mutation.graphql';
|
||||
import retryPipelineMutation from '../graphql/mutations/retry_pipeline.mutation.graphql';
|
||||
import getPipelineQuery from '../graphql/queries/get_pipeline_header_data.query.graphql';
|
||||
import TimeAgo from './pipelines_list/time_ago.vue';
|
||||
import { getQueryHeaders } from './graph/utils';
|
||||
|
||||
const DELETE_MODAL_ID = 'pipeline-delete-modal';
|
||||
|
|
@ -54,7 +54,7 @@ export default {
|
|||
GlLoadingIcon,
|
||||
GlModal,
|
||||
GlSprintf,
|
||||
TimeAgo,
|
||||
TimeAgoTooltip,
|
||||
},
|
||||
directives: {
|
||||
GlModal: GlModalDirective,
|
||||
|
|
@ -90,6 +90,8 @@ export default {
|
|||
cancelPipelineText: __('Cancel pipeline'),
|
||||
deletePipelineText: __('Delete'),
|
||||
clipboardTooltip: __('Copy commit SHA'),
|
||||
createdText: s__('Pipelines|created'),
|
||||
finishedText: s__('Pipelines|finished'),
|
||||
},
|
||||
errorTexts: {
|
||||
[LOAD_FAILURE]: __('We are currently unable to fetch data for the pipeline header.'),
|
||||
|
|
@ -441,14 +443,14 @@ export default {
|
|||
:title="$options.i18n.clipboardTooltip"
|
||||
size="small"
|
||||
/>
|
||||
<time-ago
|
||||
v-if="isFinished"
|
||||
:pipeline="pipeline"
|
||||
class="gl-display-inline gl-mb-0"
|
||||
:display-calendar-icon="false"
|
||||
font-size="gl-font-md"
|
||||
data-testid="pipeline-time-ago"
|
||||
/>
|
||||
<span v-if="inProgress" data-testid="pipeline-created-time-ago">
|
||||
{{ $options.i18n.createdText }}
|
||||
<time-ago-tooltip :time="pipeline.createdAt" />
|
||||
</span>
|
||||
<span v-if="isFinished" data-testid="pipeline-finished-time-ago">
|
||||
{{ $options.i18n.finishedText }}
|
||||
<time-ago-tooltip :time="pipeline.finishedAt" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-safe-html="refText" class="gl-mb-2" data-testid="pipeline-ref-text"></div>
|
||||
|
|
|
|||
|
|
@ -14,11 +14,6 @@ export default {
|
|||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
displayCalendarIcon: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true,
|
||||
},
|
||||
fontSize: {
|
||||
type: String,
|
||||
required: false,
|
||||
|
|
@ -50,13 +45,7 @@ export default {
|
|||
</p>
|
||||
|
||||
<p v-if="finishedTime" class="finished-at gl-display-inline-flex gl-align-items-center">
|
||||
<gl-icon
|
||||
v-if="displayCalendarIcon"
|
||||
name="calendar"
|
||||
class="gl-mr-2"
|
||||
:size="12"
|
||||
data-testid="calendar-icon"
|
||||
/>
|
||||
<gl-icon name="calendar" class="gl-mr-2" :size="12" data-testid="calendar-icon" />
|
||||
|
||||
<time
|
||||
v-gl-tooltip
|
||||
|
|
|
|||
|
|
@ -96,13 +96,6 @@ module Namespaces
|
|||
traversal_ids.present?
|
||||
end
|
||||
|
||||
def use_traversal_ids_for_self_and_hierarchy?
|
||||
return false unless use_traversal_ids?
|
||||
return false unless Feature.enabled?(:use_traversal_ids_for_self_and_hierarchy, root_ancestor)
|
||||
|
||||
traversal_ids.present?
|
||||
end
|
||||
|
||||
def use_traversal_ids_for_ancestors?
|
||||
return false unless use_traversal_ids?
|
||||
return false unless Feature.enabled?(:use_traversal_ids_for_ancestors, root_ancestor)
|
||||
|
|
@ -150,7 +143,7 @@ module Namespaces
|
|||
end
|
||||
|
||||
def self_and_hierarchy
|
||||
return super unless use_traversal_ids_for_self_and_hierarchy?
|
||||
return super unless use_traversal_ids?
|
||||
|
||||
self_and_descendants.or(ancestors)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
module Users
|
||||
class BanService < BannedUserBaseService
|
||||
extend ::Gitlab::Utils::Override
|
||||
|
||||
private
|
||||
|
||||
def update_user(user)
|
||||
|
|
@ -15,6 +17,11 @@ module Users
|
|||
def action
|
||||
:ban
|
||||
end
|
||||
|
||||
override :track_event
|
||||
def track_event(user)
|
||||
experiment(:phone_verification_for_low_risk_users, user: user).track(:banned)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ module Users
|
|||
|
||||
if update_user(user)
|
||||
log_event(user)
|
||||
track_event(user)
|
||||
success
|
||||
else
|
||||
messages = user.errors.full_messages
|
||||
|
|
@ -23,6 +24,9 @@ module Users
|
|||
|
||||
attr_reader :current_user
|
||||
|
||||
# Overridden in Users::BanService
|
||||
def track_event(_); end
|
||||
|
||||
def state_error(user)
|
||||
error(_("You cannot %{action} %{state} users." % { action: action.to_s, state: user.state }), :forbidden)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -6,10 +6,10 @@
|
|||
%p.form-text.text-muted= _('URL must be percent-encoded if necessary.')
|
||||
.form-group
|
||||
= form.label :token, _('Secret token'), class: 'label-bold'
|
||||
= form.text_field :token, class: 'form-control gl-form-input gl-max-w-48'
|
||||
= form.password_field :token, value: hook.masked_token, autocomplete: 'new-password', class: 'form-control gl-form-input gl-max-w-48'
|
||||
%p.form-text.text-muted= _('Use this token to validate received payloads.')
|
||||
.form-group
|
||||
= form.label :url, _('Trigger'), class: 'label-bold'
|
||||
= form.label :url, _('Trigger'), class: 'label-bold gl-mb-0'
|
||||
.form-text.text-secondary.gl-mb-5= _('System hooks are triggered on sets of events like creating a project or adding an SSH key. You can also enable extra triggers, such as push events.')
|
||||
%fieldset.form-group
|
||||
= form.gitlab_ui_checkbox_component :repository_update_events, _('Repository update events'),
|
||||
|
|
|
|||
|
|
@ -3,17 +3,16 @@
|
|||
|
||||
= render 'shared/web_hooks/hook_errors', hook: @hook
|
||||
|
||||
.row.gl-mt-3
|
||||
.col-lg-3
|
||||
= render 'shared/web_hooks/title_and_docs', hook: @hook
|
||||
.gl-mt-5
|
||||
= render 'shared/web_hooks/title_and_docs', hook: @hook
|
||||
|
||||
.col-lg-9.gl-mb-3
|
||||
= gitlab_ui_form_for @hook, as: :hook, url: admin_hook_path do |f|
|
||||
= render partial: 'form', locals: { form: f, hook: @hook }
|
||||
.form-actions
|
||||
%span>= f.submit _('Save changes'), class: 'gl-mr-3', pajamas_button: true
|
||||
= render 'shared/web_hooks/test_button', hook: @hook
|
||||
= link_to _('Delete'), admin_hook_path(@hook), method: :delete, class: 'btn gl-button btn-danger float-right', aria: { label: s_('Webhooks|Delete webhook') }, data: { confirm: s_('Webhooks|Are you sure you want to delete this webhook?'), confirm_btn_variant: 'danger' }
|
||||
= gitlab_ui_form_for @hook, as: :hook, url: admin_hook_path do |f|
|
||||
= render partial: 'form', locals: { form: f, hook: @hook }
|
||||
|
||||
%div
|
||||
= f.submit _('Save changes'), pajamas_button: true, class: 'gl-sm-mr-3'
|
||||
= render 'shared/web_hooks/test_button', hook: @hook
|
||||
= link_to _('Delete'), admin_hook_path(@hook), method: :delete, class: 'btn gl-button btn-danger gl-float-right', aria: { label: s_('Webhooks|Delete webhook') }, data: { confirm: s_('Webhooks|Are you sure you want to delete this webhook?'), confirm_btn_variant: 'danger' }
|
||||
|
||||
%hr
|
||||
|
||||
|
|
|
|||
|
|
@ -3,17 +3,16 @@
|
|||
|
||||
= render 'shared/web_hooks/hook_errors', hook: @hook
|
||||
|
||||
.row.gl-mt-3
|
||||
.col-lg-3
|
||||
= render 'shared/web_hooks/title_and_docs', hook: @hook
|
||||
.gl-mt-5
|
||||
= render 'shared/web_hooks/title_and_docs', hook: @hook
|
||||
|
||||
.col-lg-9.gl-mb-3
|
||||
= gitlab_ui_form_for [@project, @hook], as: :hook, url: project_hook_path(@project, @hook), html: { class: 'js-webhook-form' } do |f|
|
||||
= render partial: 'shared/web_hooks/form', locals: { form: f, hook: @hook }
|
||||
= gitlab_ui_form_for [@project, @hook], as: :hook, url: project_hook_path(@project, @hook), html: { class: 'js-webhook-form' } do |f|
|
||||
= render partial: 'shared/web_hooks/form', locals: { form: f, hook: @hook }
|
||||
|
||||
= f.submit _('Save changes'), pajamas_button: true
|
||||
%div
|
||||
= f.submit _('Save changes'), pajamas_button: true, class: 'gl-sm-mr-3'
|
||||
= render 'shared/web_hooks/test_button', hook: @hook
|
||||
= link_to _('Delete'), project_hook_path(@project, @hook), method: :delete, class: 'btn gl-button btn-danger float-right', aria: { label: s_('Webhooks|Delete webhook') }, data: { confirm: s_('Webhooks|Are you sure you want to delete this project hook?'), confirm_btn_variant: 'danger' }
|
||||
= link_to _('Delete'), project_hook_path(@project, @hook), method: :delete, class: 'btn gl-button btn-danger gl-float-right', aria: { label: s_('Webhooks|Delete webhook') }, data: { confirm: s_('Webhooks|Are you sure you want to delete this project hook?'), confirm_btn_variant: 'danger' }
|
||||
|
||||
%hr
|
||||
|
||||
|
|
|
|||
|
|
@ -2,10 +2,7 @@
|
|||
- link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: docs_link_url }
|
||||
- link_end = '</a>'.html_safe
|
||||
|
||||
.row.gl-mt-3.gl-mb-3
|
||||
.col-lg-3
|
||||
%h4.gl-mt-0
|
||||
= _('Recent events')
|
||||
%p= _('GitLab events trigger webhooks. Use the request details of a webhook to help troubleshoot problems. %{link_start}How do I troubleshoot?%{link_end}').html_safe % { link_start: link_start, link_end: link_end }
|
||||
.col-lg-9
|
||||
= render partial: 'shared/hook_logs/recent_deliveries_table', locals: { hook: hook, hook_logs: hook_logs }
|
||||
.gl-mt-5
|
||||
%h4.gl-my-0= _('Recent events')
|
||||
%p.gl-text-secondary= _('GitLab events trigger webhooks. Use the request details of a webhook to help troubleshoot problems. %{link_start}How do I troubleshoot?%{link_end}').html_safe % { link_start: link_start, link_end: link_end }
|
||||
= render partial: 'shared/hook_logs/recent_deliveries_table', locals: { hook: hook, hook_logs: hook_logs }
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
name: use_traversal_ids_for_self_and_hierarchy
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/76814
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/348527
|
||||
milestone: '14.7'
|
||||
type: development
|
||||
group: group::tenant scale
|
||||
default_enabled: true
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: search_index_curation_epics
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/121635
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/413605
|
||||
milestone: '16.2'
|
||||
type: ops
|
||||
group: group::global search
|
||||
default_enabled: false
|
||||
|
|
@ -15,7 +15,7 @@ options:
|
|||
- g_project_management_issue_created
|
||||
events:
|
||||
- name: g_project_management_issue_created
|
||||
unique: user_id
|
||||
unique: user.id
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ options:
|
|||
- i_code_review_user_create_mr
|
||||
events:
|
||||
- name: i_code_review_user_create_mr
|
||||
unique: user_id
|
||||
unique: user.id
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ options:
|
|||
- g_project_management_issue_created
|
||||
events:
|
||||
- name: g_project_management_issue_created
|
||||
unique: user_id
|
||||
unique: user.id
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
|
|
|
|||
|
|
@ -95,6 +95,29 @@
|
|||
"options": {
|
||||
"type": "object"
|
||||
},
|
||||
"events": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name",
|
||||
"unique"
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"unique": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"user.id",
|
||||
"project.id",
|
||||
"namespace.id"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"time_frame": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# See https://docs.gitlab.com/ee/development/migration_style_guide.html
|
||||
# for more information on how to write migrations for GitLab.
|
||||
|
||||
class PrepareRemovalIndexDeploymentsOnProjectIdSha < Gitlab::Database::Migration[2.1]
|
||||
INDEX_NAME = 'index_deployments_on_project_id_sha'
|
||||
|
||||
# TODO: Index to be destroyed synchronously in https://gitlab.com/gitlab-org/gitlab/-/issues/402512
|
||||
def up
|
||||
prepare_async_index_removal :deployments, %i[project_id sha], name: INDEX_NAME
|
||||
end
|
||||
|
||||
def down
|
||||
unprepare_async_index :deployments, %i[project_id sha], name: INDEX_NAME
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
d4c93417aef4587ba892f81b0339c5213cd6b5270478edcb1378138fd74e2787
|
||||
|
|
@ -200,7 +200,9 @@ To manually apply the patch that GitLab generated for a vulnerability:
|
|||
|
||||
NOTE:
|
||||
Security training is not available in an offline environment because it uses content from
|
||||
third-party vendors.
|
||||
third-party vendors. Some third-party training vendors may require you to sign up for a _free_ account. Sign up for an account by going to
|
||||
any of [Secure Code Warrior](https://www.securecodewarrior.com/), [Kontra](https://application.security/), or [SecureFlag](https://www.secureflag.com/).
|
||||
GitLab does not send any user information to these third-party vendors; we do send the CWE or OWASP identifier and the language name of the file extension.
|
||||
|
||||
Security training helps your developers learn how to fix vulnerabilities. Developers can view security training from selected educational providers, relevant to the detected vulnerability.
|
||||
|
||||
|
|
@ -211,6 +213,8 @@ To enable security training for vulnerabilities in your project:
|
|||
1. On the tab bar, select **Vulnerability Management**.
|
||||
1. To enable a security training provider, turn on the toggle.
|
||||
|
||||
Each integration submits the Vulnerability identifier, for example CWE or OWASP, and the language to the security training vendor. The resulting link to the vendor training is what appears in a GitLab Vulnerability.
|
||||
|
||||
## View security training for a vulnerability
|
||||
|
||||
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/6176) in GitLab 14.9.
|
||||
|
|
@ -218,8 +222,8 @@ To enable security training for vulnerabilities in your project:
|
|||
The vulnerability page may include a training link relevant to the detected vulnerability if security training is enabled.
|
||||
The availability of training depends on whether the enabled training vendor has content matching the particular vulnerability.
|
||||
Training content is requested based on the [vulnerability identifiers](../../../development/integrations/secure.md#identifiers).
|
||||
The identifier given to a vulnerability varies from one vulnerability to the next. The available training
|
||||
content varies between vendors. This means some vulnerabilities do not display training content.
|
||||
The identifier given to a vulnerability varies from one vulnerability to the next and the available training
|
||||
content varies between vendors. Some vulnerabilities do not display training content.
|
||||
Vulnerabilities with a CWE are most likely to return a training result.
|
||||
|
||||
To view the security training for a vulnerability:
|
||||
|
|
|
|||
|
|
@ -26,8 +26,8 @@ module Gitlab
|
|||
end
|
||||
end.freeze
|
||||
|
||||
NEGATIVE_ANSWERS = %w[no n].freeze
|
||||
POSITIVE_ANSWERS = %w[yes y].freeze
|
||||
NEGATIVE_ANSWERS = %w[no n No NO N].freeze
|
||||
POSITIVE_ANSWERS = %w[yes y Yes YES Y].freeze
|
||||
TOP_LEVEL_DIR = 'config'
|
||||
TOP_LEVEL_DIR_EE = 'ee'
|
||||
DESCRIPTION_MIN_LENGTH = 50
|
||||
|
|
@ -81,7 +81,7 @@ module Gitlab
|
|||
type: :string,
|
||||
optional: false,
|
||||
desc: 'Name of the event that this metric counts'
|
||||
class_option :unique_on,
|
||||
class_option :unique,
|
||||
type: :string,
|
||||
optional: false,
|
||||
desc: 'Name of the event property that this metric counts'
|
||||
|
|
@ -185,7 +185,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def key_path(time_frame)
|
||||
"count_distinct_#{options[:unique_on]}_from_#{event}_#{time_frame}"
|
||||
"count_distinct_#{options[:unique]}_from_#{event}_#{time_frame}"
|
||||
end
|
||||
|
||||
def metric_file_path(time_frame)
|
||||
|
|
@ -204,7 +204,7 @@ module Gitlab
|
|||
|
||||
validate_tiers!
|
||||
|
||||
%i[unique_on event mr section stage group].each do |option|
|
||||
%i[unique event mr section stage group].each do |option|
|
||||
raise "The option: --#{option} is missing" unless options.key? option
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -57,6 +57,20 @@ module Gitlab
|
|||
job_arguments: migration.job_arguments
|
||||
)
|
||||
|
||||
# If no rows match, the next_bounds are nil.
|
||||
# This will only happen if there are zero rows to match from the current sampling point to the end
|
||||
# of the table
|
||||
# Simulate the approach in the actual background migration worker by not sampling a batch
|
||||
# from this range.
|
||||
# (The actual worker would finish the migration, but we may find batches that can be sampled elsewhere
|
||||
# in the table)
|
||||
if next_bounds.nil?
|
||||
# If the migration has no work to do across the entire table, sampling can get stuck
|
||||
# in a loop if we don't mark the attempted batches as completed
|
||||
completed_batches << (batch_start..(batch_start + migration.batch_size))
|
||||
next
|
||||
end
|
||||
|
||||
batch_min, batch_max = next_bounds
|
||||
|
||||
job = migration.create_batched_job!(batch_min, batch_max)
|
||||
|
|
@ -65,7 +79,7 @@ module Gitlab
|
|||
|
||||
job
|
||||
end
|
||||
end
|
||||
end.reject(&:nil?) # Remove skipped batches from the lazy list of batches to test
|
||||
|
||||
job_class_name = migration.job_class_name
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@
|
|||
module Gitlab
|
||||
module InternalEvents
|
||||
UnknownEventError = Class.new(StandardError)
|
||||
MissingPropertyError = Class.new(StandardError)
|
||||
InvalidPropertyError = Class.new(StandardError)
|
||||
InvalidMethodError = Class.new(StandardError)
|
||||
|
||||
class << self
|
||||
include Gitlab::Tracking::Helpers
|
||||
|
|
@ -11,23 +12,28 @@ module Gitlab
|
|||
def track_event(event_name, **kwargs)
|
||||
raise UnknownEventError, "Unknown event: #{event_name}" unless EventDefinitions.known_event?(event_name)
|
||||
|
||||
unique_key = EventDefinitions.unique_property(event_name)
|
||||
unique_property = EventDefinitions.unique_property(event_name)
|
||||
unique_method = :id
|
||||
|
||||
unless kwargs.has_key?(unique_key)
|
||||
raise MissingPropertyError, "#{event_name} should be triggered with a '#{unique_key}' property"
|
||||
unless kwargs.has_key?(unique_property)
|
||||
raise InvalidPropertyError, "#{event_name} should be triggered with a named parameter '#{unique_property}'."
|
||||
end
|
||||
|
||||
UsageDataCounters::HLLRedisCounter.track_event(event_name, values: kwargs[unique_key])
|
||||
unless kwargs[unique_property].respond_to?(unique_method)
|
||||
raise InvalidMethodError, "'#{unique_property}' should have a '#{unique_method}' method."
|
||||
end
|
||||
|
||||
user_id = kwargs[:user_id]
|
||||
project_id = kwargs[:project_id]
|
||||
namespace_id = kwargs[:namespace_id]
|
||||
unique_value = kwargs[unique_property].public_send(unique_method) # rubocop:disable GitlabSecurity/PublicSend
|
||||
|
||||
namespace = Namespace.find(namespace_id) if namespace_id
|
||||
UsageDataCounters::HLLRedisCounter.track_event(event_name, values: unique_value)
|
||||
|
||||
user = kwargs[:user]
|
||||
project = kwargs[:project]
|
||||
namespace = kwargs[:namespace]
|
||||
|
||||
standard_context = Tracking::StandardContext.new(
|
||||
project_id: project_id,
|
||||
user_id: user_id,
|
||||
project_id: project&.id,
|
||||
user_id: user&.id,
|
||||
namespace_id: namespace&.id,
|
||||
plan_name: namespace&.actual_plan_name
|
||||
).to_context
|
||||
|
|
|
|||
|
|
@ -4,7 +4,10 @@ module Gitlab
|
|||
module InternalEvents
|
||||
module EventDefinitions
|
||||
InvalidMetricConfiguration = Class.new(StandardError)
|
||||
|
||||
class << self
|
||||
VALID_UNIQUE_VALUES = %w[user.id project.id namespace.id].freeze
|
||||
|
||||
def clear_events
|
||||
@events = nil
|
||||
end
|
||||
|
|
@ -15,7 +18,15 @@ module Gitlab
|
|||
end
|
||||
|
||||
def unique_property(event_name)
|
||||
events[event_name] || raise(InvalidMetricConfiguration, "Unique property not defined for #{event_name}")
|
||||
unique_value = events[event_name]&.to_s
|
||||
|
||||
raise(InvalidMetricConfiguration, "Unique property not defined for #{event_name}") unless unique_value
|
||||
|
||||
unless VALID_UNIQUE_VALUES.include?(unique_value)
|
||||
raise(InvalidMetricConfiguration, "Invalid unique value '#{unique_value}' for #{event_name}")
|
||||
end
|
||||
|
||||
unique_value.split('.').first.to_sym
|
||||
end
|
||||
|
||||
def known_event?(event_name)
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ module Gitlab
|
|||
|
||||
def validate!
|
||||
unless skip_validation?
|
||||
self.class.schemer.validate(attributes.stringify_keys).each do |error|
|
||||
self.class.schemer.validate(attributes.deep_stringify_keys).each do |error|
|
||||
error_message = <<~ERROR_MSG
|
||||
Error type: #{error['type']}
|
||||
Data: #{error['data']}
|
||||
|
|
|
|||
|
|
@ -208,9 +208,9 @@ module Gitlab
|
|||
|
||||
Gitlab::InternalEvents.track_event(
|
||||
event_name,
|
||||
user_id: author.id,
|
||||
project_id: project&.id,
|
||||
namespace_id: namespace&.id
|
||||
user: author,
|
||||
project: project,
|
||||
namespace: namespace
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -70,9 +70,9 @@ module Gitlab
|
|||
|
||||
Gitlab::InternalEvents.track_event(
|
||||
MR_USER_CREATE_ACTION,
|
||||
user_id: user.id,
|
||||
project_id: project.id,
|
||||
namespace_id: project.namespace_id
|
||||
user: user,
|
||||
project: project,
|
||||
namespace: project.namespace
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -33864,9 +33864,15 @@ msgstr ""
|
|||
msgid "Pipelines|Your changes have been successfully committed. Now redirecting to the new merge request page."
|
||||
msgstr ""
|
||||
|
||||
msgid "Pipelines|created"
|
||||
msgstr ""
|
||||
|
||||
msgid "Pipelines|error"
|
||||
msgstr ""
|
||||
|
||||
msgid "Pipelines|finished"
|
||||
msgstr ""
|
||||
|
||||
msgid "Pipelines|invalid"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -141,7 +141,7 @@ RSpec.describe 'Pipeline', :js, feature_category: :groups_and_projects do
|
|||
visit project_pipeline_path(project, finished_pipeline)
|
||||
|
||||
within '[data-testid="pipeline-details-header"]' do
|
||||
expect(page).to have_selector('[data-testid="pipeline-time-ago"]')
|
||||
expect(page).to have_selector('[data-testid="pipeline-finished-time-ago"]')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -151,7 +151,7 @@ RSpec.describe 'Pipeline', :js, feature_category: :groups_and_projects do
|
|||
visit_pipeline
|
||||
|
||||
within '[data-testid="pipeline-details-header"]' do
|
||||
expect(page).not_to have_selector('[data-testid="pipeline-time-ago"]')
|
||||
expect(page).not_to have_selector('[data-testid="pipeline-finished-time-ago"]')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import waitForPromises from 'helpers/wait_for_promises';
|
|||
import { TEST_HOST } from 'spec/test_constants';
|
||||
import deployKeysApp from '~/deploy_keys/components/app.vue';
|
||||
import ConfirmModal from '~/deploy_keys/components/confirm_modal.vue';
|
||||
import NavigationTabs from '~/vue_shared/components/navigation_tabs.vue';
|
||||
import eventHub from '~/deploy_keys/eventhub';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
|
||||
|
|
@ -39,6 +40,7 @@ describe('Deploy keys app component', () => {
|
|||
const findLoadingIcon = () => wrapper.find('.gl-spinner');
|
||||
const findKeyPanels = () => wrapper.findAll('.deploy-keys .gl-tabs-nav li');
|
||||
const findModal = () => wrapper.findComponent(ConfirmModal);
|
||||
const findNavigationTabs = () => wrapper.findComponent(NavigationTabs);
|
||||
|
||||
it('renders loading icon while waiting for request', async () => {
|
||||
mock.onGet(TEST_ENDPOINT).reply(() => new Promise());
|
||||
|
|
@ -74,55 +76,61 @@ describe('Deploy keys app component', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('re-fetches deploy keys when enabling a key', async () => {
|
||||
const key = data.public_keys[0];
|
||||
await mountComponent();
|
||||
jest.spyOn(wrapper.vm.service, 'getKeys').mockImplementation(() => {});
|
||||
jest.spyOn(wrapper.vm.service, 'enableKey').mockImplementation(() => Promise.resolve());
|
||||
|
||||
eventHub.$emit('enable.key', key);
|
||||
|
||||
await nextTick();
|
||||
expect(wrapper.vm.service.enableKey).toHaveBeenCalledWith(key.id);
|
||||
expect(wrapper.vm.service.getKeys).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('re-fetches deploy keys when disabling a key', async () => {
|
||||
const key = data.public_keys[0];
|
||||
await mountComponent();
|
||||
jest.spyOn(wrapper.vm.service, 'getKeys').mockImplementation(() => {});
|
||||
jest.spyOn(wrapper.vm.service, 'disableKey').mockImplementation(() => Promise.resolve());
|
||||
|
||||
eventHub.$emit('disable.key', key, () => {});
|
||||
|
||||
await nextTick();
|
||||
expect(findModal().props('visible')).toBe(true);
|
||||
findModal().vm.$emit('remove');
|
||||
|
||||
await nextTick();
|
||||
expect(wrapper.vm.service.disableKey).toHaveBeenCalledWith(key.id);
|
||||
expect(wrapper.vm.service.getKeys).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('calls disableKey when removing a key', async () => {
|
||||
const key = data.public_keys[0];
|
||||
await mountComponent();
|
||||
jest.spyOn(wrapper.vm.service, 'getKeys').mockImplementation(() => {});
|
||||
jest.spyOn(wrapper.vm.service, 'disableKey').mockImplementation(() => Promise.resolve());
|
||||
|
||||
eventHub.$emit('remove.key', key, () => {});
|
||||
|
||||
await nextTick();
|
||||
expect(findModal().props('visible')).toBe(true);
|
||||
findModal().vm.$emit('remove');
|
||||
|
||||
await nextTick();
|
||||
expect(wrapper.vm.service.disableKey).toHaveBeenCalledWith(key.id);
|
||||
expect(wrapper.vm.service.getKeys).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('hasKeys returns true when there are keys', async () => {
|
||||
await mountComponent();
|
||||
expect(wrapper.vm.hasKeys).toEqual(3);
|
||||
|
||||
expect(findNavigationTabs().exists()).toBe(true);
|
||||
expect(findLoadingIcon().exists()).toBe(false);
|
||||
});
|
||||
|
||||
describe('enabling and disabling keys', () => {
|
||||
const key = data.public_keys[0];
|
||||
let getMethodMock;
|
||||
let putMethodMock;
|
||||
|
||||
const removeKey = async (keyEvent) => {
|
||||
eventHub.$emit(keyEvent, key, () => {});
|
||||
|
||||
await nextTick();
|
||||
expect(findModal().props('visible')).toBe(true);
|
||||
findModal().vm.$emit('remove');
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
getMethodMock = jest.spyOn(axios, 'get');
|
||||
putMethodMock = jest.spyOn(axios, 'put');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
getMethodMock.mockClear();
|
||||
putMethodMock.mockClear();
|
||||
});
|
||||
|
||||
it('re-fetches deploy keys when enabling a key', async () => {
|
||||
await mountComponent();
|
||||
|
||||
eventHub.$emit('enable.key', key);
|
||||
|
||||
expect(putMethodMock).toHaveBeenCalledWith(`${TEST_ENDPOINT}/${key.id}/enable`);
|
||||
expect(getMethodMock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('re-fetches deploy keys when disabling a key', async () => {
|
||||
await mountComponent();
|
||||
|
||||
await removeKey('disable.key');
|
||||
|
||||
expect(putMethodMock).toHaveBeenCalledWith(`${TEST_ENDPOINT}/${key.id}/disable`);
|
||||
expect(getMethodMock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('calls disableKey when removing a key', async () => {
|
||||
await mountComponent();
|
||||
|
||||
await removeKey('remove.key');
|
||||
|
||||
expect(putMethodMock).toHaveBeenCalledWith(`${TEST_ENDPOINT}/${key.id}/disable`);
|
||||
expect(getMethodMock).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import waitForPromises from 'helpers/wait_for_promises';
|
|||
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
||||
import PipelineDetailsHeader from '~/pipelines/components/pipeline_details_header.vue';
|
||||
import { BUTTON_TOOLTIP_RETRY, BUTTON_TOOLTIP_CANCEL } from '~/pipelines/constants';
|
||||
import TimeAgo from '~/pipelines/components/pipelines_list/time_ago.vue';
|
||||
import CiBadgeLink from '~/vue_shared/components/ci_badge_link.vue';
|
||||
import cancelPipelineMutation from '~/pipelines/graphql/mutations/cancel_pipeline.mutation.graphql';
|
||||
import deletePipelineMutation from '~/pipelines/graphql/mutations/delete_pipeline.mutation.graphql';
|
||||
|
|
@ -59,8 +58,10 @@ describe('Pipeline details header', () => {
|
|||
const findAlert = () => wrapper.findComponent(GlAlert);
|
||||
const findStatus = () => wrapper.findComponent(CiBadgeLink);
|
||||
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
|
||||
const findTimeAgo = () => wrapper.findComponent(TimeAgo);
|
||||
const findAllBadges = () => wrapper.findAllComponents(GlBadge);
|
||||
const findDeleteModal = () => wrapper.findComponent(GlModal);
|
||||
const findCreatedTimeAgo = () => wrapper.findByTestId('pipeline-created-time-ago');
|
||||
const findFinishedTimeAgo = () => wrapper.findByTestId('pipeline-finished-time-ago');
|
||||
const findPipelineName = () => wrapper.findByTestId('pipeline-name');
|
||||
const findCommitTitle = () => wrapper.findByTestId('pipeline-commit-title');
|
||||
const findTotalJobs = () => wrapper.findByTestId('total-jobs');
|
||||
|
|
@ -71,7 +72,6 @@ describe('Pipeline details header', () => {
|
|||
const findRetryButton = () => wrapper.findByTestId('retry-pipeline');
|
||||
const findCancelButton = () => wrapper.findByTestId('cancel-pipeline');
|
||||
const findDeleteButton = () => wrapper.findByTestId('delete-pipeline');
|
||||
const findDeleteModal = () => wrapper.findComponent(GlModal);
|
||||
const findPipelineUserLink = () => wrapper.findByTestId('pipeline-user-link');
|
||||
const findPipelineDuration = () => wrapper.findByTestId('pipeline-duration-text');
|
||||
|
||||
|
|
@ -232,12 +232,20 @@ describe('Pipeline details header', () => {
|
|||
expect(findComputeCredits().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('displays time ago', async () => {
|
||||
it('does not display created time ago', async () => {
|
||||
createComponent();
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
expect(findTimeAgo().exists()).toBe(true);
|
||||
expect(findCreatedTimeAgo().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('displays finished time ago', async () => {
|
||||
createComponent();
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
expect(findFinishedTimeAgo().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('displays pipeline duartion text', async () => {
|
||||
|
|
@ -262,8 +270,8 @@ describe('Pipeline details header', () => {
|
|||
expect(findComputeCredits().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('does not display time ago', () => {
|
||||
expect(findTimeAgo().exists()).toBe(false);
|
||||
it('does not display finished time ago', () => {
|
||||
expect(findFinishedTimeAgo().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('does not display pipeline duration text', () => {
|
||||
|
|
@ -273,6 +281,10 @@ describe('Pipeline details header', () => {
|
|||
it('displays pipeline running text', () => {
|
||||
expect(findPipelineRunningText()).toBe('In progress, queued for 3,600 seconds');
|
||||
});
|
||||
|
||||
it('displays created time ago', () => {
|
||||
expect(findCreatedTimeAgo().exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('running pipeline with duration', () => {
|
||||
|
|
|
|||
|
|
@ -65,22 +65,11 @@ describe('Timeago component', () => {
|
|||
expect(time.exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('should display calendar icon by default', () => {
|
||||
it('should display calendar icon', () => {
|
||||
createComponent({ duration: 0, finished_at: '2017-04-26T12:40:23.277Z' });
|
||||
|
||||
expect(findCalendarIcon().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('should hide calendar icon if correct prop is passed', () => {
|
||||
createComponent(
|
||||
{ duration: 0, finished_at: '2017-04-26T12:40:23.277Z' },
|
||||
{
|
||||
displayCalendarIcon: false,
|
||||
},
|
||||
);
|
||||
|
||||
expect(findCalendarIcon().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('without finishedTime', () => {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ RSpec.describe Gitlab::Analytics::InternalEventsGenerator, :silence_stdout, feat
|
|||
let(:section) { "analytics" }
|
||||
let(:mr) { "https://gitlab.com/some-group/some-project/-/merge_requests/123" }
|
||||
let(:event) { "view_analytics_dashboard" }
|
||||
let(:unique_on) { "user_id" }
|
||||
let(:unique) { "user_id" }
|
||||
let(:time_frames) { %w[7d] }
|
||||
let(:include_default_identifiers) { 'yes' }
|
||||
let(:options) do
|
||||
|
|
@ -27,11 +27,11 @@ RSpec.describe Gitlab::Analytics::InternalEventsGenerator, :silence_stdout, feat
|
|||
stage: stage,
|
||||
section: section,
|
||||
event: event,
|
||||
unique_on: unique_on
|
||||
unique: unique
|
||||
}.stringify_keys
|
||||
end
|
||||
|
||||
let(:key_path_7d) { "count_distinct_#{unique_on}_from_#{event}_7d" }
|
||||
let(:key_path_7d) { "count_distinct_#{unique}_from_#{event}_7d" }
|
||||
let(:metric_definition_path_7d) { Dir.glob(File.join(temp_dir, "metrics/counts_7d/#{key_path_7d}.yml")).first }
|
||||
let(:metric_definition_7d) do
|
||||
{
|
||||
|
|
@ -211,8 +211,8 @@ RSpec.describe Gitlab::Analytics::InternalEventsGenerator, :silence_stdout, feat
|
|||
|
||||
context 'without obligatory parameter' do
|
||||
it 'raises error', :aggregate_failures do
|
||||
%w[unique_on event mr section stage group].each do |option|
|
||||
expect { described_class.new([], options.without(option)).invoke_all }
|
||||
%w[unique event mr section stage group].each do |option|
|
||||
expect { described_class.new([], options.without(option)).invoke_all }
|
||||
.to raise_error(RuntimeError)
|
||||
end
|
||||
end
|
||||
|
|
@ -241,7 +241,7 @@ RSpec.describe Gitlab::Analytics::InternalEventsGenerator, :silence_stdout, feat
|
|||
|
||||
context 'for multiple time frames' do
|
||||
let(:time_frames) { %w[7d 28d] }
|
||||
let(:key_path_28d) { "count_distinct_#{unique_on}_from_#{event}_28d" }
|
||||
let(:key_path_28d) { "count_distinct_#{unique}_from_#{event}_28d" }
|
||||
let(:metric_definition_path_28d) { Dir.glob(File.join(temp_dir, "metrics/counts_28d/#{key_path_28d}.yml")).first }
|
||||
let(:metric_definition_28d) do
|
||||
metric_definition_7d.merge(
|
||||
|
|
@ -260,7 +260,7 @@ RSpec.describe Gitlab::Analytics::InternalEventsGenerator, :silence_stdout, feat
|
|||
|
||||
context 'with default time frames' do
|
||||
let(:time_frames) { nil }
|
||||
let(:key_path_28d) { "count_distinct_#{unique_on}_from_#{event}_28d" }
|
||||
let(:key_path_28d) { "count_distinct_#{unique}_from_#{event}_28d" }
|
||||
let(:metric_definition_path_28d) { Dir.glob(File.join(temp_dir, "metrics/counts_28d/#{key_path_28d}.yml")).first }
|
||||
let(:metric_definition_28d) do
|
||||
metric_definition_7d.merge(
|
||||
|
|
|
|||
|
|
@ -153,6 +153,25 @@ RSpec.describe Gitlab::Database::Migrations::TestBatchedBackgroundRunner, :freez
|
|||
expect(calls.size).to eq(1)
|
||||
end
|
||||
|
||||
it 'does not sample a job if there are zero rows to sample' do
|
||||
calls = []
|
||||
define_background_migration(migration_name, with_base_class: true, scoping: ->(relation) {
|
||||
relation.none
|
||||
}) do |*args|
|
||||
calls << args
|
||||
end
|
||||
|
||||
queue_migration(migration_name, table_name, :id,
|
||||
job_interval: 5.minutes,
|
||||
batch_size: num_rows_in_table * 2,
|
||||
sub_batch_size: num_rows_in_table * 2)
|
||||
|
||||
described_class.new(result_dir: result_dir, connection: connection,
|
||||
from_id: from_id).run_jobs(for_duration: 3.minutes)
|
||||
|
||||
expect(calls.count).to eq(0)
|
||||
end
|
||||
|
||||
context 'with multiple jobs to run' do
|
||||
let(:last_id) do
|
||||
Gitlab::Database::SharedModel.using_connection(connection) do
|
||||
|
|
|
|||
|
|
@ -29,20 +29,38 @@ RSpec.describe Gitlab::InternalEvents::EventDefinitions, feature_category: :prod
|
|||
end
|
||||
|
||||
describe ".unique_property" do
|
||||
context 'when event does have unique property', :aggregate_failures do
|
||||
let(:events1) { { 'event1' => :user_id } }
|
||||
let(:events2) { { 'event2' => :project_id } }
|
||||
context 'when event has valid unique value with a period', :aggregate_failures do
|
||||
let(:events1) { { 'event1' => :'user.id' } }
|
||||
let(:events2) { { 'event2' => :'project.id' } }
|
||||
|
||||
it 'is returned' do
|
||||
expect(described_class.unique_property('event1')).to eq(:user_id)
|
||||
expect(described_class.unique_property('event2')).to eq(:project_id)
|
||||
expect(described_class.unique_property('event1')).to eq(:user)
|
||||
expect(described_class.unique_property('event2')).to eq(:project)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when event has no periods in unique property', :aggregate_failures do
|
||||
let(:events1) { { 'event1' => :plan_id } }
|
||||
|
||||
it 'fails' do
|
||||
expect { described_class.unique_property('event1') }
|
||||
.to raise_error(described_class::InvalidMetricConfiguration, /Invalid unique value/)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when event has more than one period in unique property' do
|
||||
let(:events1) { { 'event1' => :'project.namespace.id' } }
|
||||
|
||||
it 'fails' do
|
||||
expect { described_class.unique_property('event1') }
|
||||
.to raise_error(described_class::InvalidMetricConfiguration, /Invalid unique value/)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when event does not have unique property' do
|
||||
it 'unique_property fails' do
|
||||
it 'unique fails' do
|
||||
expect { described_class.unique_property('event1') }
|
||||
.to raise_error(described_class::InvalidMetricConfiguration)
|
||||
.to raise_error(described_class::InvalidMetricConfiguration, /Unique property not defined for/)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ RSpec.describe Gitlab::InternalEvents, :snowplow, feature_category: :product_ana
|
|||
allow(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception)
|
||||
allow(Gitlab::UsageDataCounters::HLLRedisCounter).to receive(:track_event)
|
||||
allow(Gitlab::Tracking).to receive(:tracker).and_return(fake_snowplow)
|
||||
allow(Gitlab::InternalEvents::EventDefinitions).to receive(:unique_property).and_return(:user_id)
|
||||
allow(Gitlab::InternalEvents::EventDefinitions).to receive(:unique_property).and_return(:user)
|
||||
allow(fake_snowplow).to receive(:event)
|
||||
end
|
||||
|
||||
|
|
@ -36,7 +36,7 @@ RSpec.describe Gitlab::InternalEvents, :snowplow, feature_category: :product_ana
|
|||
end
|
||||
|
||||
let_it_be(:user) { build(:user, id: 1) }
|
||||
let_it_be(:project) { build(:project) }
|
||||
let_it_be(:project) { build(:project, id: 2) }
|
||||
let_it_be(:namespace) { project.namespace }
|
||||
|
||||
let(:fake_snowplow) { instance_double(Gitlab::Tracking::Destinations::Snowplow) }
|
||||
|
|
@ -44,7 +44,7 @@ RSpec.describe Gitlab::InternalEvents, :snowplow, feature_category: :product_ana
|
|||
let(:unique_value) { user.id }
|
||||
|
||||
it 'updates both RedisHLL and Snowplow', :aggregate_failures do
|
||||
params = { user_id: user.id, project_id: project.id, namespace_id: namespace.id }
|
||||
params = { user: user, project: project, namespace: namespace }
|
||||
described_class.track_event(event_name, **params)
|
||||
|
||||
expect_redis_hll_tracking(event_name)
|
||||
|
|
@ -52,7 +52,7 @@ RSpec.describe Gitlab::InternalEvents, :snowplow, feature_category: :product_ana
|
|||
end
|
||||
|
||||
it 'rescues error', :aggregate_failures do
|
||||
params = { user_id: user.id, project_id: project.id, namespace_id: namespace.id }
|
||||
params = { user: user, project: project, namespace: namespace }
|
||||
error = StandardError.new("something went wrong")
|
||||
allow(fake_snowplow).to receive(:event).and_raise(error)
|
||||
|
||||
|
|
@ -66,18 +66,18 @@ RSpec.describe Gitlab::InternalEvents, :snowplow, feature_category: :product_ana
|
|||
expect { described_class.track_event(event_name, **params) }.not_to raise_error
|
||||
end
|
||||
|
||||
it 'fails on unknown event', :aggregate_failures do
|
||||
it 'logs error on unknown event', :aggregate_failures do
|
||||
expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception)
|
||||
.with(described_class::UnknownEventError, event_name: 'unknown_event', kwargs: {})
|
||||
|
||||
expect { described_class.track_event('unknown_event') }.not_to raise_error
|
||||
end
|
||||
|
||||
it 'raises on missing property' do
|
||||
it 'logs error on missing property' do
|
||||
expect { described_class.track_event(event_name, merge_request_id: 1) }.not_to raise_error
|
||||
|
||||
expect(Gitlab::ErrorTracking).to have_received(:track_and_raise_for_dev_exception)
|
||||
.with(described_class::MissingPropertyError, event_name: event_name, kwargs: { merge_request_id: 1 })
|
||||
.with(described_class::InvalidPropertyError, event_name: event_name, kwargs: { merge_request_id: 1 })
|
||||
end
|
||||
|
||||
context 'when unique property is missing' do
|
||||
|
|
@ -95,21 +95,38 @@ RSpec.describe Gitlab::InternalEvents, :snowplow, feature_category: :product_ana
|
|||
|
||||
context 'when unique key is defined' do
|
||||
let(:event_name) { 'p_ci_templates_terraform_base_latest' }
|
||||
let(:user_id) { 1 }
|
||||
let(:project_id) { 2 }
|
||||
let(:unique_value) { project_id }
|
||||
let(:unique_value) { project.id }
|
||||
let(:property_name) { :project }
|
||||
|
||||
before do
|
||||
allow(Gitlab::InternalEvents::EventDefinitions).to receive(:unique_property)
|
||||
.with('p_ci_templates_terraform_base_latest')
|
||||
.and_return(:project_id)
|
||||
.with(event_name)
|
||||
.and_return(property_name)
|
||||
end
|
||||
|
||||
it 'is used when logging to RedisHLL' do
|
||||
described_class.track_event(event_name, user_id: user_id, project_id: project_id)
|
||||
it 'is used when logging to RedisHLL', :aggregate_failures do
|
||||
described_class.track_event(event_name, user: user, project: project)
|
||||
|
||||
expect_redis_hll_tracking(event_name)
|
||||
expect_snowplow_tracking(event_name)
|
||||
end
|
||||
|
||||
context 'when property is missing' do
|
||||
it 'logs error' do
|
||||
expect { described_class.track_event(event_name, merge_request_id: 1) }.not_to raise_error
|
||||
|
||||
expect(Gitlab::ErrorTracking).to have_received(:track_and_raise_for_dev_exception)
|
||||
.with(described_class::InvalidPropertyError, event_name: event_name, kwargs: { merge_request_id: 1 })
|
||||
end
|
||||
end
|
||||
|
||||
context 'when method does not exist on property' do
|
||||
it 'logs error on missing method' do
|
||||
expect { described_class.track_event(event_name, project: "a_string") }.not_to raise_error
|
||||
|
||||
expect(Gitlab::ErrorTracking).to have_received(:track_and_raise_for_dev_exception)
|
||||
.with(described_class::InvalidMethodError, event_name: event_name, kwargs: { project: 'a_string' })
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1673,32 +1673,6 @@ RSpec.describe Namespace, feature_category: :groups_and_projects do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#use_traversal_ids_for_self_and_hierarchy?' do
|
||||
let_it_be(:namespace, reload: true) { create(:namespace) }
|
||||
|
||||
subject { namespace.use_traversal_ids_for_self_and_hierarchy? }
|
||||
|
||||
it { is_expected.to eq true }
|
||||
|
||||
it_behaves_like 'disabled feature flag when traversal_ids is blank'
|
||||
|
||||
context 'when use_traversal_ids_for_self_and_hierarchy feature flag is false' do
|
||||
before do
|
||||
stub_feature_flags(use_traversal_ids_for_self_and_hierarchy: false)
|
||||
end
|
||||
|
||||
it { is_expected.to eq false }
|
||||
end
|
||||
|
||||
context 'when use_traversal_ids? feature flag is false' do
|
||||
before do
|
||||
stub_feature_flags(use_traversal_ids: false)
|
||||
end
|
||||
|
||||
it { is_expected.to eq false }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#users_with_descendants' do
|
||||
let(:user_a) { create(:user) }
|
||||
let(:user_b) { create(:user) }
|
||||
|
|
|
|||
|
|
@ -38,6 +38,13 @@ RSpec.describe Users::BanService, feature_category: :user_management do
|
|||
|
||||
ban_user
|
||||
end
|
||||
|
||||
it 'tracks the event', :experiment do
|
||||
expect(experiment(:phone_verification_for_low_risk_users))
|
||||
.to track(:banned).on_next_instance.with_context(user: user)
|
||||
|
||||
ban_user
|
||||
end
|
||||
end
|
||||
|
||||
context 'when failed' do
|
||||
|
|
|
|||
|
|
@ -2,11 +2,13 @@
|
|||
|
||||
module Database
|
||||
module MigrationTestingHelpers
|
||||
def define_background_migration(name)
|
||||
klass = Class.new do
|
||||
def define_background_migration(name, with_base_class: false, scoping: nil)
|
||||
klass = Class.new(with_base_class ? Gitlab::BackgroundMigration::BatchedMigrationJob : Object) do
|
||||
# Can't simply def perform here as we won't have access to the block,
|
||||
# similarly can't define_method(:perform, &block) here as it would change the block receiver
|
||||
define_method(:perform) { |*args| yield(*args) }
|
||||
|
||||
scope_to(scoping) if scoping
|
||||
end
|
||||
stub_const("Gitlab::BackgroundMigration::#{name}", klass)
|
||||
klass
|
||||
|
|
|
|||
Loading…
Reference in New Issue