Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2023-11-16 12:07:22 +00:00
parent 34a59635a9
commit 77ded523f1
32 changed files with 615 additions and 581 deletions

View File

@ -150,7 +150,7 @@ review-deploy-sample-projects:
.review-stop-base:
extends: .review-workflow-base
timeout: 15min
timeout: 30min
environment:
action: stop
variables:

View File

@ -1,62 +0,0 @@
<script>
import { GlButton, GlAlert, GlModalDirective } from '@gitlab/ui';
export default {
components: {
GlAlert,
GlButton,
},
directives: {
GlModalDirective,
},
props: {
mergeable: {
type: Boolean,
required: true,
},
resolutionPath: {
type: String,
required: true,
},
},
};
</script>
<template>
<div>
<gl-alert
:dismissible="false"
:title="__('There are merge conflicts')"
variant="warning"
class="gl-mb-5"
>
<p class="gl-mb-2">
{{ __('The comparison view may be inaccurate due to merge conflicts.') }}
</p>
<p class="gl-mb-0">
{{
__(
'Resolve these conflicts, or ask someone with write access to this repository to resolve them locally.',
)
}}
</p>
<template #actions>
<gl-button
v-if="resolutionPath"
:href="resolutionPath"
variant="confirm"
class="gl-mr-3 gl-alert-action"
>
{{ __('Resolve conflicts') }}
</gl-button>
<gl-button
v-if="mergeable"
v-gl-modal-directive="'modal-merge-info'"
class="gl-alert-action"
>
{{ __('Resolve locally') }}
</gl-button>
</template>
</gl-alert>
</div>
</template>

View File

@ -28,7 +28,6 @@ import initLogoAnimation from './logo';
import initBreadcrumbs from './breadcrumb';
import initPersistentUserCallouts from './persistent_user_callouts';
import { initUserTracking, initDefaultTrackers } from './tracking';
import { initSidebarTracking } from './pages/shared/nav/sidebar_tracking';
import GlFieldErrors from './gl_field_errors';
import initUserPopovers from './user_popovers';
import initBroadcastNotifications from './broadcast_notification';
@ -96,7 +95,6 @@ function deferredInitialisation() {
initBroadcastNotifications();
initPersistentUserCallouts();
initDefaultTrackers();
initSidebarTracking();
initFeatureHighlight();
initCopyCodeButton();
initGitlabVersionCheck();

View File

@ -1,44 +0,0 @@
function onSidebarLinkClick() {
const setDataTrackAction = (element, action) => {
element.dataset.trackAction = action;
};
const setDataTrackExtra = (element, value) => {
const SIDEBAR_COLLAPSED = 'Collapsed';
const SIDEBAR_EXPANDED = 'Expanded';
const sidebarCollapsed = document
.querySelector('.nav-sidebar')
.classList.contains('js-sidebar-collapsed')
? SIDEBAR_COLLAPSED
: SIDEBAR_EXPANDED;
element.dataset.trackExtra = JSON.stringify({
sidebar_display: sidebarCollapsed,
menu_display: value,
});
};
const EXPANDED = 'Expanded';
const FLY_OUT = 'Fly out';
const CLICK_MENU_ACTION = 'click_menu';
const CLICK_MENU_ITEM_ACTION = 'click_menu_item';
const parentElement = this.parentNode;
const subMenuList = parentElement.closest('.sidebar-sub-level-items');
if (subMenuList) {
const isFlyOut = subMenuList.classList.contains('fly-out-list') ? FLY_OUT : EXPANDED;
setDataTrackExtra(parentElement, isFlyOut);
setDataTrackAction(parentElement, CLICK_MENU_ITEM_ACTION);
} else {
const isFlyOut = parentElement.classList.contains('is-showing-fly-out') ? FLY_OUT : EXPANDED;
setDataTrackExtra(parentElement, isFlyOut);
setDataTrackAction(parentElement, CLICK_MENU_ACTION);
}
}
export const initSidebarTracking = () => {
document.querySelectorAll('.nav-sidebar li[data-track-label] > a').forEach((link) => {
link.addEventListener('click', onSidebarLinkClick);
});
};

View File

@ -29,6 +29,7 @@
# repository_storage: string
# not_aimed_for_deletion: boolean
# full_paths: string[]
# organization_id: int
#
class ProjectsFinder < UnionFinder
include CustomAttributesFilter
@ -95,6 +96,7 @@ class ProjectsFinder < UnionFinder
collection = by_language(collection)
collection = by_feature_availability(collection)
collection = by_updated_at(collection)
collection = by_organization_id(collection)
by_repository_storage(collection)
end
@ -293,6 +295,10 @@ class ProjectsFinder < UnionFinder
items
end
def by_organization_id(items)
params[:organization_id].present? ? items.in_organization(params[:organization_id]) : items
end
def finder_params
return {} unless min_access_level?

View File

@ -89,10 +89,26 @@ class Event < ApplicationRecord
scope :for_wiki_page, -> { where(target_type: 'WikiPage::Meta') }
scope :for_design, -> { where(target_type: 'DesignManagement::Design') }
scope :for_issue, -> { where(target_type: ISSUE_TYPES) }
scope :for_merge_request, -> { where(target_type: 'MergeRequest') }
scope :for_fingerprint, ->(fingerprint) do
fingerprint.present? ? where(fingerprint: fingerprint) : none
end
scope :for_action, ->(action) { where(action: action) }
scope :created_between, ->(start_time, end_time) { where(created_at: start_time..end_time) }
scope :count_by_dates, ->(date_interval) { group("DATE(created_at + #{date_interval})").count }
scope :contributions, -> do
contribution_actions = [actions[:pushed], actions[:commented]]
contributable_target_types = %w[MergeRequest Issue WorkItem]
target_contribution_actions = [actions[:created], actions[:closed], actions[:merged], actions[:approved]]
where(
'action IN (?) OR (target_type IN (?) AND action IN (?))',
contribution_actions,
contributable_target_types, target_contribution_actions
)
end
scope :with_associations, -> do
# We're using preload for "push_event_payload" as otherwise the association
@ -133,15 +149,6 @@ class Event < ApplicationRecord
end
end
# Update Gitlab::ContributionsCalendar#activity_dates if this changes
def contributions
where(
'action IN (?) OR (target_type IN (?) AND action IN (?))',
[actions[:pushed], actions[:commented]],
%w[MergeRequest Issue WorkItem], [actions[:created], actions[:closed], actions[:merged]]
)
end
def limit_recent(limit = 20, offset = nil)
recent.limit(limit).offset(offset)
end

View File

@ -781,6 +781,8 @@ class Project < ApplicationRecord
.order(id: :desc)
end
scope :in_organization, -> (organization) { where(organization: organization) }
enum auto_cancel_pending_pipelines: { disabled: 0, enabled: 1 }
chronic_duration_attr :build_timeout_human_readable, :build_timeout,

View File

@ -1767,7 +1767,7 @@ class User < MainClusterwide::ApplicationRecord
def contributed_projects
events = Event.select(:project_id)
.contributions.where(author_id: self)
.where("created_at > ?", Time.current - 1.year)
.created_after(Time.current - 1.year)
.distinct
.reorder(nil)

View File

@ -0,0 +1,8 @@
---
name: contributions_calendar_refactoring
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/134991
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/429648
milestone: '16.6'
type: development
group: group::tenant scale
default_enabled: false

View File

@ -445,7 +445,7 @@ First start with creating a file named `build_script`:
cat <<EOF > build_script
git clone https://gitlab.com/gitlab-org/gitlab-runner.git /builds/gitlab-org/gitlab-runner
cd /builds/gitlab-org/gitlab-runner
make
make runner-bin-host
EOF
```
@ -456,8 +456,7 @@ Instead of `make`, you could run the command which is specific to your project.
Then create some service containers:
```shell
docker run -d --name service-mysql mysql:latest
docker run -d --name service-postgres postgres:latest
docker run -d --name service-redis redis:latest
```
The previous commands create two service containers. The service container named `service-mysql` uses the latest MySQL image. The one named `service-postgres` uses the latest PostgreSQL image. Both service containers run in the background (`-d`).
@ -466,7 +465,7 @@ Finally, create a build container by executing the `build_script` file we
created earlier:
```shell
docker run --name build -i --link=service-mysql:mysql --link=service-postgres:postgres ruby:2.6 /bin/bash < build_script
docker run --name build -i --link=service-redis:redis golang:latest /bin/bash < build_script
```
The above command creates a container named `build` that's spawned from

View File

@ -63,6 +63,12 @@ Gitlab::InternalEvents.track_event(
This method automatically increments all RedisHLL metrics relating to the event `i_code_review_user_apply_suggestion`, and sends a corresponding Snowplow event with all named arguments and standard context (SaaS only).
If you have defined a metric with a `unique` property such as `unique: project.id` it is required that you provide the `project` argument.
It is encouraged to fill out as many of `user`, `namespace` and `project` as possible as it increases the data quality and make it easier to define metrics in the future.
If a `project` but no `namespace` is provided, the `project.namespace` is used as the `namespace` for the event.
### Frontend tracking
#### Vue components

View File

@ -336,13 +336,13 @@ period is prorated from the date of purchase through the end of the subscription
To add seats to a subscription:
1. Log in to the [Customers Portal](https://customers.gitlab.com/).
1. Navigate to the **Manage Purchases** page.
1. Sign in to the [Customers Portal](https://customers.gitlab.com/).
1. Go to the **Manage Purchases** page.
1. Select **Add more seats** on the relevant subscription card.
1. Enter the number of additional users.
1. Select **Proceed to checkout**.
1. Review the **Subscription Upgrade Detail**. The system lists the total price for all users on the system and a credit for what you've already paid. You are only be charged for the net change.
1. Select **Confirm Upgrade**.
1. Review the **Purchase summary** section. The system lists the total price for all users on the system and a credit for what you've already paid. You are only charged for the net change.
1. Enter your payment information.
1. Select **Purchase seats**.
A payment receipt is emailed to you, which you can also access in the Customers Portal under [**View invoices**](https://customers.gitlab.com/receipts).
@ -359,9 +359,7 @@ You should follow these steps during renewal:
1. Prior to the renewal date, prune any inactive or unwanted users by [blocking them](../../administration/moderate_users.md#block-a-user).
1. Determine if you have a need for user growth in the upcoming subscription.
1. Log in to the [Customers Portal](https://customers.gitlab.com/customers/sign_in) and select the **Renew** button beneath your existing subscription.
The **Renew** button remains disabled (grayed-out) until 15 days before a subscription expires.
You can hover your mouse on the **Renew** button to see the date when it will become active.
1. Sign in to the [Customers Portal](https://customers.gitlab.com/customers/sign_in) and beneath your existing subscription, select **Renew**. The **Renew** button displays only 15 days before a subscription expires. If there are more than 15 days before the subscription expires, select **Subscription actions** (**{ellipsis_v}**), then select **Renew subscription** to view the date when you can renew.
NOTE:
If you need to change your [GitLab tier](https://about.gitlab.com/pricing/), contact our sales team with [the sales contact form](https://about.gitlab.com/sales/) for assistance as this can't be done in the Customers Portal.
@ -396,10 +394,8 @@ You can view and download your renewal invoice on the Customers Portal [View inv
To view or change automatic subscription renewal (at the same tier as the
previous period), sign in to the [Customers Portal](https://customers.gitlab.com/customers/sign_in), and:
- If a **Turn on auto-renew** button is displayed, your subscription was canceled
previously. Select it to resume automatic renewal.
- If a **Cancel subscription** button is displayed, your subscription is set to automatically
renew at the end of the subscription period. Select it to cancel automatic renewal.
- If the subscription card displays `Expires on DATE`, your subscription is not set to automatically renew. To enable automatic renewal, in **Subscription actions** (**{ellipsis_v}**), select **Turn on auto-renew**.
- If the subscription card displays `Autorenews on DATE`, your subscription is set to automatically renew at the end of the subscription period. To cancel automatic renewal, in **Subscription actions** (**{ellipsis_v}**), select **Cancel subscription**.
If you have difficulty during the renewal process, contact the
[Support team](https://support.gitlab.com/hc/en-us/requests/new?ticket_form_id=360000071293) for assistance.
@ -416,9 +412,8 @@ There are several options to renew a subscription for fewer seats, as long as th
To upgrade your [GitLab tier](https://about.gitlab.com/pricing/):
1. Log in to the [Customers Portal](https://customers.gitlab.com/customers/sign_in).
1. Select the **Upgrade** button on the relevant subscription card on the
[Manage purchases](https://customers.gitlab.com/subscriptions) page.
1. Sign in to the [Customers Portal](https://customers.gitlab.com/customers/sign_in).
1. Select **Upgrade** on the relevant subscription card.
1. Select the desired upgrade.
1. Confirm the active form of payment, or add a new form of payment.
1. Select the **I accept the Privacy Policy and Terms of Service** checkbox.

View File

@ -6,7 +6,7 @@ module API
class ModelVersions < ::API::Base
feature_category :mlops
resource :model_versions do
resource 'model-versions' do
desc 'Fetch model version by name and version' do
success Entities::Ml::Mlflow::ModelVersions::Responses::Get
detail 'https://mlflow.org/docs/2.6.0/rest-api.html#get-modelversion'

View File

@ -3,6 +3,7 @@
module Gitlab
class ContributionsCalendar
include TimeZoneHelper
include ::Gitlab::Utils::StrongMemoize
attr_reader :contributor
attr_reader :current_user
@ -16,18 +17,23 @@ module Gitlab
.execute(current_user, ignore_visibility: @contributor.include_private_contributions?)
end
# rubocop: disable CodeReuse/ActiveRecord
def activity_dates
return {} if @projects.empty?
return @activity_dates if @activity_dates.present?
return {} if projects.empty?
start_time = @contributor_time_instance.years_ago(1).beginning_of_day
end_time = @contributor_time_instance.end_of_day
date_interval = "INTERVAL '#{@contributor_time_instance.utc_offset} seconds'"
if Feature.enabled?(:contributions_calendar_refactoring)
return contributions_between(start_time, end_time).count_by_dates(date_interval)
end
# TODO: Remove after feature flag `contributions_calendar_refactoring` is rolled out
# See https://gitlab.com/gitlab-org/gitlab/-/issues/429648
# rubocop: disable CodeReuse/ActiveRecord -- will be removed
# Can't use Event.contributions here because we need to check 3 different
# project_features for the (currently) 3 different contribution types
# project_features for the (currently) 4 different contribution types
repo_events = events_created_between(start_time, end_time, :repository)
.where(action: :pushed)
issue_events = events_created_between(start_time, end_time, :issues)
@ -42,55 +48,110 @@ module Gitlab
.from_union([repo_events, issue_events, mr_events, note_events], remove_duplicates: false)
.group(:date)
.map(&:attributes)
# rubocop: enable CodeReuse/ActiveRecord
@activity_dates = events.each_with_object(Hash.new { |h, k| h[k] = 0 }) do |event, activities|
events.each_with_object(Hash.new { |h, k| h[k] = 0 }) do |event, activities|
activities[event["date"]] += event["num_events"]
end
end
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
def events_by_date(date)
return Event.none unless can_read_cross_project?
date_in_time_zone = date.in_time_zone(@contributor_time_instance.time_zone)
if Feature.enabled?(:contributions_calendar_refactoring)
return contributions_between(date_in_time_zone.beginning_of_day, date_in_time_zone.end_of_day).with_associations
end
# TODO: Remove after feature flag `contributions_calendar_refactoring` is rolled out
# See https://gitlab.com/gitlab-org/gitlab/-/issues/429648
# rubocop: disable CodeReuse/ActiveRecord -- will be removed
Event.contributions.where(author_id: contributor.id)
.where(created_at: date_in_time_zone.beginning_of_day..date_in_time_zone.end_of_day)
.where(project_id: projects)
.with_associations
end
# rubocop: enable CodeReuse/ActiveRecord
def starting_year
@contributor_time_instance.years_ago(1).year
end
def starting_month
@contributor_time_instance.month
# rubocop: enable CodeReuse/ActiveRecord
end
private
def contributions_between(start_time, end_time)
# Can't use Event.contributions here because we need to check 3 different
# project_features for the (currently) 4 different contribution types
repo_events =
project_events_created_between(start_time, end_time, features: :repository)
.for_action(:pushed)
issue_events =
project_events_created_between(start_time, end_time, features: :issues)
.for_issue
.for_action(%i[created closed])
mr_events =
project_events_created_between(start_time, end_time, features: :merge_requests)
.for_merge_request
.for_action(%i[merged created closed approved])
note_events =
project_events_created_between(start_time, end_time, features: %i[issues merge_requests])
.for_action(:commented)
Event.from_union([repo_events, issue_events, mr_events, note_events], remove_duplicates: false)
end
def can_read_cross_project?
Ability.allowed?(current_user, :read_cross_project)
end
# rubocop: disable CodeReuse/ActiveRecord
def events_created_between(start_time, end_time, feature)
# rubocop: disable CodeReuse/ActiveRecord -- no need to move this to ActiveRecord model
def project_events_created_between(start_time, end_time, features:)
Array(features).reduce(Event.none) do |events, feature|
events.or(contribution_events(start_time, end_time).where(project_id: authed_projects(feature)))
end
end
# rubocop: enable CodeReuse/ActiveRecord
def authed_projects(feature)
strong_memoize("#{feature}_projects") do
# no need to check features access of current user, if the contributor opted-in
# to show all private events anyway - otherwise they would get filtered out again
next contributed_project_ids if contributor.include_private_contributions?
# rubocop: disable CodeReuse/ActiveRecord -- no need to move this to ActiveRecord model
ProjectFeature
.with_feature_available_for_user(feature, current_user)
.where(project_id: contributed_project_ids)
.pluck(:project_id)
# rubocop: enable CodeReuse/ActiveRecord
end
end
# rubocop: disable CodeReuse/ActiveRecord -- no need to move this to ActiveRecord model
def contributed_project_ids
# re-running the contributed projects query in each union is expensive, so
# use IN(project_ids...) instead. It's the intersection of two users so
# the list will be (relatively) short
@contributed_project_ids ||= projects.distinct.pluck(:id)
end
# rubocop: enable CodeReuse/ActiveRecord
def contribution_events(start_time, end_time)
contributor.events.created_between(start_time, end_time)
end
# TODO: Remove after feature flag `contributions_calendar_refactoring` is rolled out
# See https://gitlab.com/gitlab-org/gitlab/-/issues/429648
# rubocop: disable CodeReuse/ActiveRecord -- will be removed
def events_created_between(start_time, end_time, feature)
# no need to check feature access of current user, if the contributor opted-in
# to show all private events anyway - otherwise they would get filtered out again
authed_projects = if @contributor.include_private_contributions?
@contributed_project_ids
authed_projects = if contributor.include_private_contributions?
contributed_project_ids
else
ProjectFeature
.with_feature_available_for_user(feature, current_user)
.where(project_id: @contributed_project_ids)
.where(project_id: contributed_project_ids)
.reorder(nil)
.select(:project_id)
end

View File

@ -70,8 +70,8 @@ module Gitlab
# returns a Hash, then the return value of that method will be merged into
# `extra`. Exceptions can use this mechanism to provide structured data
# to sentry in addition to their message and back-trace.
def track_and_raise_exception(exception, extra = {})
process_exception(exception, extra: extra)
def track_and_raise_exception(exception, extra = {}, tags = {})
process_exception(exception, extra: extra, tags: tags)
raise exception
end
@ -90,8 +90,8 @@ module Gitlab
#
# Provide an issue URL for follow up.
# as `issue_url: 'http://gitlab.com/gitlab-org/gitlab/issues/111'`
def track_and_raise_for_dev_exception(exception, extra = {})
process_exception(exception, extra: extra)
def track_and_raise_for_dev_exception(exception, extra = {}, tags = {})
process_exception(exception, extra: extra, tags: tags)
raise exception if should_raise_for_dev?
end
@ -102,8 +102,8 @@ module Gitlab
# returns a Hash, then the return value of that method will be merged into
# `extra`. Exceptions can use this mechanism to provide structured data
# to sentry in addition to their message and back-trace.
def track_exception(exception, extra = {})
process_exception(exception, extra: extra)
def track_exception(exception, extra = {}, tags = {})
process_exception(exception, extra: extra, tags: tags)
end
# This should be used when you only want to log the exception,
@ -157,8 +157,8 @@ module Gitlab
end
end
def process_exception(exception, extra:, trackers: default_trackers)
context_payload = Gitlab::ErrorTracking::ContextPayloadGenerator.generate(exception, extra)
def process_exception(exception, extra:, tags: {}, trackers: default_trackers)
context_payload = Gitlab::ErrorTracking::ContextPayloadGenerator.generate(exception, extra, tags)
trackers.each do |tracker|
tracker.capture_exception(exception, **context_payload)

View File

@ -3,14 +3,14 @@
module Gitlab
module ErrorTracking
class ContextPayloadGenerator
def self.generate(exception, extra = {})
new.generate(exception, extra)
def self.generate(exception, extra = {}, tags = {})
new.generate(exception, extra, tags)
end
def generate(exception, extra = {})
def generate(exception, extra = {}, tags = {})
{
extra: extra_payload(exception, extra),
tags: tags_payload,
tags: tags_payload(tags),
user: user_payload
}
end
@ -31,12 +31,14 @@ module Gitlab
filter.filter(parameters)
end
def tags_payload
extra_tags_from_env.merge!(
program: Gitlab.process_name,
locale: I18n.locale,
feature_category: current_context['meta.feature_category'],
Labkit::Correlation::CorrelationId::LOG_KEY.to_sym => Labkit::Correlation::CorrelationId.current_id
def tags_payload(tags)
tags.merge(
extra_tags_from_env.merge!(
program: Gitlab.process_name,
locale: I18n.locale,
feature_category: current_context['meta.feature_category'],
Labkit::Correlation::CorrelationId::LOG_KEY.to_sym => Labkit::Correlation::CorrelationId.current_id
)
)
end

View File

@ -44,8 +44,8 @@ module Gitlab
TRANSLATION_LEVELS = {
'bg' => 0,
'cs_CZ' => 0,
'da_DK' => 29,
'de' => 97,
'da_DK' => 28,
'de' => 95,
'en' => 100,
'eo' => 0,
'es' => 28,
@ -56,18 +56,18 @@ module Gitlab
'it' => 1,
'ja' => 98,
'ko' => 23,
'nb_NO' => 21,
'nb_NO' => 20,
'nl_NL' => 0,
'pl_PL' => 3,
'pt_BR' => 57,
'ro_RO' => 76,
'pt_BR' => 60,
'ro_RO' => 74,
'ru' => 21,
'si_LK' => 12,
'si_LK' => 11,
'tr_TR' => 8,
'uk' => 52,
'uk' => 51,
'zh_CN' => 99,
'zh_HK' => 1,
'zh_TW' => 100
'zh_TW' => 99
}.freeze
private_constant :TRANSLATION_LEVELS

View File

@ -4,7 +4,7 @@ module Gitlab
module InternalEvents
UnknownEventError = Class.new(StandardError)
InvalidPropertyError = Class.new(StandardError)
InvalidMethodError = Class.new(StandardError)
InvalidPropertyTypeError = Class.new(StandardError)
class << self
include Gitlab::Tracking::Helpers
@ -12,6 +12,13 @@ module Gitlab
def track_event(event_name, send_snowplow_event: true, **kwargs)
raise UnknownEventError, "Unknown event: #{event_name}" unless EventDefinitions.known_event?(event_name)
validate_property!(kwargs, :user, User)
validate_property!(kwargs, :namespace, Namespaces::UserNamespace, Group)
validate_property!(kwargs, :project, Project)
project = kwargs[:project]
kwargs[:namespace] ||= project.namespace if project
increase_total_counter(event_name)
increase_weekly_total_counter(event_name)
update_unique_counter(event_name, kwargs)
@ -23,6 +30,14 @@ module Gitlab
private
def validate_property!(kwargs, property_name, *class_names)
return unless kwargs.has_key?(property_name)
return if kwargs[property_name].nil?
return if class_names.include?(kwargs[property_name].class)
raise InvalidPropertyTypeError, "#{property_name} should be an instance of #{class_names.join(', ')}"
end
def increase_total_counter(event_name)
redis_counter_key =
Gitlab::Usage::Metrics::Instrumentations::TotalCountMetric.redis_key(event_name)
@ -45,10 +60,6 @@ module Gitlab
raise InvalidPropertyError, "#{event_name} should be triggered with a named parameter '#{unique_property}'."
end
unless kwargs[unique_property].respond_to?(unique_method)
raise InvalidMethodError, "'#{unique_property}' should have a '#{unique_method}' method."
end
unique_value = kwargs[unique_property].public_send(unique_method) # rubocop:disable GitlabSecurity/PublicSend
UsageDataCounters::HLLRedisCounter.track_event(event_name, values: unique_value)

View File

@ -18,10 +18,11 @@ module Gitlab
# https://github.com/puma/puma/pull/3094
status_code ||= 500
if Raven.configuration.capture_allowed?
Raven.capture_exception(ex, tags: { handler: 'puma_low_level' },
extra: { puma_env: env, status_code: status_code })
end
Gitlab::ErrorTracking.track_exception(
ex,
{ puma_env: env, status_code: status_code },
{ handler: 'puma_low_level' }
)
# note the below is just a Rack response
[status_code, {}, message]

View File

@ -40680,9 +40680,6 @@ msgstr ""
msgid "Resolve locally"
msgstr ""
msgid "Resolve these conflicts, or ask someone with write access to this repository to resolve them locally."
msgstr ""
msgid "Resolve thread"
msgstr ""
@ -48156,9 +48153,6 @@ msgstr ""
msgid "The commit does not exist"
msgstr ""
msgid "The comparison view may be inaccurate due to merge conflicts."
msgstr ""
msgid "The complete DevOps platform. One application with endless possibilities. Organizations rely on GitLabs source code management, CI/CD, security, and more to deliver software rapidly."
msgstr ""
@ -48652,9 +48646,6 @@ msgstr ""
msgid "There are currently no target branch rules"
msgstr ""
msgid "There are merge conflicts"
msgstr ""
msgid "There are no GPG keys associated with this account."
msgstr ""

View File

@ -72,12 +72,34 @@ function delete_helm_release() {
fi
if deploy_exists "${namespace}" "${release}"; then
echoinfo "[$(date '+%H:%M:%S')] Release exists. Deleting it first."
helm uninstall --namespace="${namespace}" "${release}"
fi
if namespace_exists "${namespace}"; then
echoinfo "Deleting namespace ${namespace}..." true
kubectl delete namespace "${namespace}" --wait
echoinfo "[$(date '+%H:%M:%S')] Deleting namespace ${namespace}..." true
# Capture status of command, and check whether the status was successful.
if ! kubectl delete namespace "${namespace}" --wait --timeout=1200s; then
echoerr
echoerr "Could not delete the namespace ${namespace} in time."
echoerr
echoerr "It can happen that some resources cannot be deleted right away, causing a delay in the namespace deletion."
echoerr
echoerr "You can see further below which resources are blocking the deletion of the namespace (under DEBUG information)"
echoerr
echoerr "If retrying the job does not fix the issue, please contact Engineering Productivity for further investigation."
echoerr
echoerr "DEBUG INFORMATION:"
echoerr
echoerr "RUNBOOK: https://gitlab.com/gitlab-org/quality/engineering-productivity/team/-/blob/main/runbooks/review-apps.md#namespace-stuck-in-terminating-state"
echoerr
kubectl describe namespace "${namespace}"
echoinfo
kubectl api-resources --verbs=list --namespaced -o name | xargs -n 1 kubectl get -n "${namespace}"
exit 1
fi
fi
}

View File

@ -482,6 +482,19 @@ RSpec.describe ProjectsFinder, feature_category: :groups_and_projects do
it { is_expected.to match_array([internal_project]) }
end
describe 'filter by organization_id' do
let_it_be(:organization) { create(:organization) }
let_it_be(:organization_project) { create(:project, organization: organization) }
let(:params) { { organization_id: organization.id } }
before do
organization_project.add_maintainer(current_user)
end
it { is_expected.to match_array([organization_project]) }
end
describe 'when with_issues_enabled is true' do
let(:params) { { with_issues_enabled: true } }

View File

@ -1,58 +0,0 @@
import { shallowMount, mount } from '@vue/test-utils';
import MergeConflictWarning from '~/diffs/components/merge_conflict_warning.vue';
const propsData = {
limited: true,
mergeable: true,
resolutionPath: 'a-path',
};
function findResolveButton(wrapper) {
return wrapper.find('.gl-alert-actions a.gl-button:first-child');
}
function findLocalMergeButton(wrapper) {
return wrapper.find('.gl-alert-actions button.gl-button:last-child');
}
describe('MergeConflictWarning', () => {
let wrapper;
const createComponent = (props = {}, { full } = { full: false }) => {
const mounter = full ? mount : shallowMount;
wrapper = mounter(MergeConflictWarning, {
propsData: { ...propsData, ...props },
});
};
it.each`
present | resolutionPath
${false} | ${''}
${true} | ${'some-path'}
`(
'toggles the resolve conflicts button based on the provided resolutionPath "$resolutionPath"',
({ present, resolutionPath }) => {
createComponent({ resolutionPath }, { full: true });
const resolveButton = findResolveButton(wrapper);
expect(resolveButton.exists()).toBe(present);
if (present) {
expect(resolveButton.attributes('href')).toBe(resolutionPath);
}
},
);
it.each`
present | mergeable
${false} | ${false}
${true} | ${true}
`(
'toggles the local merge button based on the provided mergeable property "$mergable"',
({ present, mergeable }) => {
createComponent({ mergeable }, { full: true });
const localMerge = findLocalMergeButton(wrapper);
expect(localMerge.exists()).toBe(present);
},
);
});

View File

@ -1,160 +0,0 @@
import { setHTMLFixture } from 'helpers/fixtures';
import { initSidebarTracking } from '~/pages/shared/nav/sidebar_tracking';
describe('~/pages/shared/nav/sidebar_tracking.js', () => {
beforeEach(() => {
setHTMLFixture(`
<aside class="nav-sidebar">
<div class="nav-sidebar-inner-scroll">
<ul class="sidebar-top-level-items">
<li data-track-label="project_information_menu" class="home">
<a aria-label="Project information" class="shortcuts-project-information has-sub-items" href="">
<span class="nav-icon-container">
<svg class="s16" data-testid="project-icon">
<use xlink:href="/assets/icons-1b2dadc4c3d49797908ba67b8f10da5d63dd15d859bde28d66fb60bbb97a4dd5.svg#project"></use>
</svg>
</span>
<span class="nav-item-name">Project information</span>
</a>
<ul class="sidebar-sub-level-items">
<li class="fly-out-top-item">
<a aria-label="Project information" href="#">
<strong class="fly-out-top-item-name">Project information</strong>
</a>
</li>
<li class="divider fly-out-top-item"></li>
<li data-track-label="activity" class="">
<a aria-label="Activity" class="shortcuts-project-activity" href=#">
<span>Activity</span>
</a>
</li>
<li data-track-label="labels" class="">
<a aria-label="Labels" href="#">
<span>Labels</span>
</a>
</li>
<li data-track-label="members" class="">
<a aria-label="Members" href="#">
<span>Members</span>
</a>
</li>
</ul>
</li>
</ul>
</div>
</aside>
`);
initSidebarTracking();
});
describe('sidebar is not collapsed', () => {
describe('menu is not expanded', () => {
it('sets the proper data tracking attributes when clicking on menu', () => {
const menu = document.querySelector('li[data-track-label="project_information_menu"]');
const menuLink = menu.querySelector('a');
menu.classList.add('is-over', 'is-showing-fly-out');
menuLink.click();
expect(menu).toHaveTrackingAttributes({
action: 'click_menu',
extra: JSON.stringify({
sidebar_display: 'Expanded',
menu_display: 'Fly out',
}),
});
});
it('sets the proper data tracking attributes when clicking on submenu', () => {
const menu = document.querySelector('li[data-track-label="activity"]');
const menuLink = menu.querySelector('a');
const submenuList = document.querySelector('ul.sidebar-sub-level-items');
submenuList.classList.add('fly-out-list');
menuLink.click();
expect(menu).toHaveTrackingAttributes({
action: 'click_menu_item',
extra: JSON.stringify({
sidebar_display: 'Expanded',
menu_display: 'Fly out',
}),
});
});
});
describe('menu is expanded', () => {
it('sets the proper data tracking attributes when clicking on menu', () => {
const menu = document.querySelector('li[data-track-label="project_information_menu"]');
const menuLink = menu.querySelector('a');
menu.classList.add('active');
menuLink.click();
expect(menu).toHaveTrackingAttributes({
action: 'click_menu',
extra: JSON.stringify({
sidebar_display: 'Expanded',
menu_display: 'Expanded',
}),
});
});
it('sets the proper data tracking attributes when clicking on submenu', () => {
const menu = document.querySelector('li[data-track-label="activity"]');
const menuLink = menu.querySelector('a');
menu.classList.add('active');
menuLink.click();
expect(menu).toHaveTrackingAttributes({
action: 'click_menu_item',
extra: JSON.stringify({
sidebar_display: 'Expanded',
menu_display: 'Expanded',
}),
});
});
});
});
describe('sidebar is collapsed', () => {
beforeEach(() => {
document.querySelector('aside.nav-sidebar').classList.add('js-sidebar-collapsed');
});
it('sets the proper data tracking attributes when clicking on menu', () => {
const menu = document.querySelector('li[data-track-label="project_information_menu"]');
const menuLink = menu.querySelector('a');
menu.classList.add('is-over', 'is-showing-fly-out');
menuLink.click();
expect(menu).toHaveTrackingAttributes({
action: 'click_menu',
extra: JSON.stringify({
sidebar_display: 'Collapsed',
menu_display: 'Fly out',
}),
});
});
it('sets the proper data tracking attributes when clicking on submenu', () => {
const menu = document.querySelector('li[data-track-label="activity"]');
const menuLink = menu.querySelector('a');
const submenuList = document.querySelector('ul.sidebar-sub-level-items');
submenuList.classList.add('fly-out-list');
menuLink.click();
expect(menu).toHaveTrackingAttributes({
action: 'click_menu_item',
extra: JSON.stringify({
sidebar_display: 'Collapsed',
menu_display: 'Fly out',
}),
});
});
});
});

View File

@ -19,9 +19,9 @@ RSpec.describe Gitlab::ContributionsCalendar, feature_category: :user_profile do
end
end
let_it_be(:feature_project) do
let_it_be(:public_project_with_private_issues) do
create(:project, :public, :issues_private) do |project|
create(:project_member, user: contributor, project: project).project
create(:project_member, user: contributor, project: project)
end
end
@ -45,7 +45,12 @@ RSpec.describe Gitlab::ContributionsCalendar, feature_category: :user_profile do
end
def create_event(project, day, hour = 0, action = :created, target_symbol = :issue)
targets[project] ||= create(target_symbol, project: project, author: contributor)
targets[project] ||=
if target_symbol == :merge_request
create(:merge_request, source_project: project, author: contributor)
else
create(target_symbol, project: project, author: contributor)
end
Event.create!(
project: project,
@ -58,49 +63,65 @@ RSpec.describe Gitlab::ContributionsCalendar, feature_category: :user_profile do
end
describe '#activity_dates', :aggregate_failures do
it "returns a hash of date => count" do
create_event(public_project, last_week)
create_event(public_project, last_week)
create_event(public_project, today)
work_item_event = create_event(private_project, today, 0, :created, :work_item)
# make sure the target is a work item as we want to include those in the count
expect(work_item_event.target_type).to eq('WorkItem')
expect(calendar(contributor).activity_dates).to eq(last_week => 2, today => 2)
end
context "when the user has opted-in for private contributions" do
before do
contributor.update_column(:include_private_contributions, true)
end
it "shows private and public events to all users" do
create_event(private_project, today)
shared_examples_for 'returns a hash of date => count' do
specify do
create_event(public_project, last_week)
create_event(public_project, last_week)
create_event(public_project, today)
work_item_event = create_event(private_project, today, 0, :created, :work_item)
expect(calendar.activity_dates[today]).to eq(2)
expect(calendar(user).activity_dates[today]).to eq(2)
expect(calendar(contributor).activity_dates[today]).to eq(2)
end
# tests for bug https://gitlab.com/gitlab-org/gitlab/-/merge_requests/74826
it "still counts correct with feature access levels set to private" do
create_event(private_project, today)
private_project.project_feature.update_attribute(:issues_access_level, ProjectFeature::PRIVATE)
private_project.project_feature.update_attribute(:repository_access_level, ProjectFeature::PRIVATE)
private_project.project_feature.update_attribute(:merge_requests_access_level, ProjectFeature::PRIVATE)
expect(calendar.activity_dates[today]).to eq(1)
expect(calendar(user).activity_dates[today]).to eq(1)
expect(calendar(contributor).activity_dates[today]).to eq(1)
end
it "does not fail if there are no contributed projects" do
expect(calendar.activity_dates[today]).to eq(nil)
# make sure the target is a work item as we want to include those in the count
expect(work_item_event.target_type).to eq('WorkItem')
expect(calendar(contributor).activity_dates).to eq(last_week => 2, today => 2)
end
end
shared_examples 'private contribution events' do
context "when the user has opted-in for private contributions" do
before do
contributor.update_column(:include_private_contributions, true)
end
it "shows private and public events to all users" do
create_event(private_project, today)
create_event(public_project, today)
expect(calendar.activity_dates[today]).to eq(2)
expect(calendar(user).activity_dates[today]).to eq(2)
expect(calendar(contributor).activity_dates[today]).to eq(2)
end
# tests for bug https://gitlab.com/gitlab-org/gitlab/-/merge_requests/74826
it "still counts correct with feature access levels set to private" do
create_event(private_project, today)
private_project.project_feature.update_attribute(:issues_access_level, ProjectFeature::PRIVATE)
private_project.project_feature.update_attribute(:repository_access_level, ProjectFeature::PRIVATE)
private_project.project_feature.update_attribute(:merge_requests_access_level, ProjectFeature::PRIVATE)
expect(calendar.activity_dates[today]).to eq(1)
expect(calendar(user).activity_dates[today]).to eq(1)
expect(calendar(contributor).activity_dates[today]).to eq(1)
end
it "does not fail if there are no contributed projects" do
expect(calendar.activity_dates[today]).to eq(nil)
end
end
end
context 'when contributions_calendar_refactoring feature flag is disabled' do
before do
stub_feature_flags(contributions_calendar_refactoring: false)
end
it_behaves_like 'returns a hash of date => count'
include_examples 'private contribution events'
end
it_behaves_like 'returns a hash of date => count'
include_examples 'private contribution events'
it "counts the diff notes on merge request" do
create_event(public_project, today, 0, :commented, :diff_note_on_merge_request)
@ -114,6 +135,15 @@ RSpec.describe Gitlab::ContributionsCalendar, feature_category: :user_profile do
expect(calendar(contributor).activity_dates[today]).to eq(2)
end
it "counts merge request events" do
create_event(public_project, today, 0, :created, :merge_request)
create_event(public_project, today, 1, :closed, :merge_request)
create_event(public_project, today, 2, :approved, :merge_request)
create_event(public_project, today, 3, :merged, :merge_request)
expect(calendar(contributor).activity_dates[today]).to eq(4)
end
context "when events fall under different dates depending on the system time zone" do
before do
create_event(public_project, today, 1)
@ -189,19 +219,46 @@ RSpec.describe Gitlab::ContributionsCalendar, feature_category: :user_profile do
it "only shows private events to authorized users" do
e1 = create_event(public_project, today)
e2 = create_event(private_project, today)
e3 = create_event(feature_project, today)
e3 = create_event(public_project_with_private_issues, today, 0, :created, :issue)
create_event(public_project, last_week)
expect(calendar.events_by_date(today)).to contain_exactly(e1, e3)
expect(calendar.events_by_date(today)).to contain_exactly(e1)
expect(calendar(contributor).events_by_date(today)).to contain_exactly(e1, e2, e3)
end
context 'when contributions_calendar_refactoring feature flag is disabled' do
before do
stub_feature_flags(contributions_calendar_refactoring: false)
end
it "only shows private events to authorized users" do
e1 = create_event(public_project, today)
e2 = create_event(private_project, today)
e3 = create_event(public_project_with_private_issues, today, 0, :created, :issue)
create_event(public_project, last_week)
expect(calendar.events_by_date(today)).to contain_exactly(e1, e3)
expect(calendar(contributor).events_by_date(today)).to contain_exactly(e1, e2, e3)
end
end
it "includes diff notes on merge request" do
e1 = create_event(public_project, today, 0, :commented, :diff_note_on_merge_request)
expect(calendar.events_by_date(today)).to contain_exactly(e1)
end
it 'includes merge request events' do
mr_created_event = create_event(public_project, today, 0, :created, :merge_request)
mr_closed_event = create_event(public_project, today, 1, :closed, :merge_request)
mr_approved_event = create_event(public_project, today, 2, :approved, :merge_request)
mr_merged_event = create_event(public_project, today, 3, :merged, :merge_request)
expect(calendar.events_by_date(today)).to contain_exactly(
mr_created_event, mr_closed_event, mr_approved_event, mr_merged_event
)
end
context 'when the user cannot read cross project' do
before do
allow(Ability).to receive(:allowed?).and_call_original
@ -215,40 +272,4 @@ RSpec.describe Gitlab::ContributionsCalendar, feature_category: :user_profile do
end
end
end
describe '#starting_year' do
let(:travel_time) { Time.find_zone('UTC').local(2020, 12, 31, 19, 0, 0) }
context "when the contributor's timezone is not set" do
it "is the start of last year in the system timezone" do
expect(calendar.starting_year).to eq(2019)
end
end
context "when the contributor's timezone is set to Sydney" do
let(:contributor) { create(:user, { timezone: 'Sydney' }) }
it "is the start of last year in Sydney" do
expect(calendar.starting_year).to eq(2020)
end
end
end
describe '#starting_month' do
let(:travel_time) { Time.find_zone('UTC').local(2020, 12, 31, 19, 0, 0) }
context "when the contributor's timezone is not set" do
it "is the start of this month in the system timezone" do
expect(calendar.starting_month).to eq(12)
end
end
context "when the contributor's timezone is set to Sydney" do
let(:contributor) { create(:user, { timezone: 'Sydney' }) }
it "is the start of this month in Sydney" do
expect(calendar.starting_month).to eq(1)
end
end
end
end

View File

@ -64,8 +64,11 @@ RSpec.describe Gitlab::ErrorTracking::ContextPayloadGenerator do
end
context 'when the GITLAB_SENTRY_EXTRA_TAGS env is a JSON hash' do
it 'includes those tags in all events' do
before do
stub_env('GITLAB_SENTRY_EXTRA_TAGS', { foo: 'bar', baz: 'quux' }.to_json)
end
it 'includes those tags in all events' do
payload = {}
Gitlab::ApplicationContext.with_context(feature_category: 'feature_a') do
@ -87,6 +90,26 @@ RSpec.describe Gitlab::ErrorTracking::ContextPayloadGenerator do
generator.generate(exception, extra)
end
context 'with generated tags' do
it 'includes all tags' do
payload = {}
Gitlab::ApplicationContext.with_context(feature_category: 'feature_a') do
payload = generator.generate(exception, extra, { 'mytag' => '123' })
end
expect(payload[:tags]).to eql(
correlation_id: 'cid',
locale: 'en',
program: 'test',
feature_category: 'feature_a',
'foo' => 'bar',
'baz' => 'quux',
'mytag' => '123'
)
end
end
end
context 'when the GITLAB_SENTRY_EXTRA_TAGS env is not a JSON hash' do

View File

@ -97,6 +97,27 @@ RSpec.describe Gitlab::ErrorTracking, feature_category: :shared do
)
end.to raise_error(RuntimeError, /boom/)
end
context 'with tags' do
let(:tags) { { 'mytag' => 2 } }
before do
sentry_payload[:tags].merge!(tags)
end
it 'includes additional tags' do
expect(Raven).to receive(:capture_exception).with(exception, sentry_payload)
expect(Sentry).to receive(:capture_exception).with(exception, sentry_payload)
expect do
described_class.track_and_raise_for_dev_exception(
exception,
{ issue_url: issue_url, some_other_info: 'info' },
tags
)
end.to raise_error(RuntimeError, /boom/)
end
end
end
context 'when exceptions for dev should not be raised' do
@ -181,8 +202,10 @@ RSpec.describe Gitlab::ErrorTracking, feature_category: :shared do
end
describe '.track_exception' do
let(:tags) { {} }
subject(:track_exception) do
described_class.track_exception(exception, extra)
described_class.track_exception(exception, extra, tags)
end
before do
@ -207,6 +230,18 @@ RSpec.describe Gitlab::ErrorTracking, feature_category: :shared do
expect(Gitlab::ErrorTracking::Logger).to have_received(:error).with(logger_payload)
end
context 'with tags' do
let(:tags) { { 'mytag' => 2 } }
it 'includes the tags' do
track_exception
expect(Gitlab::ErrorTracking::Logger).to have_received(:error).with(
hash_including({ 'tags.mytag' => 2 })
)
end
end
context 'with filterable parameters' do
let(:extra) { { test: 1, my_token: 'test' } }

View File

@ -12,11 +12,23 @@ RSpec.describe Gitlab::InternalEvents, :snowplow, feature_category: :product_ana
allow(redis).to receive(:incr)
allow(Gitlab::Redis::SharedState).to receive(:with).and_yield(redis)
allow(Gitlab::Tracking).to receive(:tracker).and_return(fake_snowplow)
allow(Gitlab::InternalEvents::EventDefinitions).to receive(:unique_property).and_return(:user)
allow(Gitlab::InternalEvents::EventDefinitions).to receive(:unique_property).and_return(unique_property)
allow(fake_snowplow).to receive(:event)
end
def expect_redis_hll_tracking(event_name)
shared_examples 'an event that logs an error' do
it 'logs an error' do
described_class.track_event(event_name, **event_kwargs)
expect(Gitlab::ErrorTracking).to have_received(:track_and_raise_for_dev_exception)
.with(described_class::InvalidPropertyTypeError,
event_name: event_name,
kwargs: event_kwargs
)
end
end
def expect_redis_hll_tracking
expect(Gitlab::UsageDataCounters::HLLRedisCounter).to have_received(:track_event)
.with(event_name, values: unique_value)
end
@ -29,7 +41,7 @@ RSpec.describe Gitlab::InternalEvents, :snowplow, feature_category: :product_ana
end
end
def expect_snowplow_tracking(event_name)
def expect_snowplow_tracking(expected_namespace = nil)
service_ping_context = Gitlab::Tracking::ServicePingContext
.new(data_source: :redis_hll, event: event_name)
.to_context
@ -38,34 +50,125 @@ RSpec.describe Gitlab::InternalEvents, :snowplow, feature_category: :product_ana
expect(SnowplowTracker::SelfDescribingJson).to have_received(:new)
.with(service_ping_context[:schema], service_ping_context[:data]).at_least(:once)
# Add test for creation of both contexts
contexts = [instance_of(SnowplowTracker::SelfDescribingJson), instance_of(SnowplowTracker::SelfDescribingJson)]
expect(fake_snowplow).to have_received(:event) do |category, provided_event_name, args|
expect(category).to eq('InternalEventTracking')
expect(provided_event_name).to eq(event_name)
expect(fake_snowplow).to have_received(:event)
.with('InternalEventTracking', event_name, context: contexts)
contexts = args[:context]&.map(&:to_json)
# Verify Standard Context
standard_context = contexts.find do |c|
c[:schema] == Gitlab::Tracking::StandardContext::GITLAB_STANDARD_SCHEMA_URL
end
validate_standard_context(standard_context, expected_namespace)
# Verify Service Ping context
service_ping_context = contexts.find { |c| c[:schema] == Gitlab::Tracking::ServicePingContext::SCHEMA_URL }
validate_service_ping_context(service_ping_context)
end
end
def validate_standard_context(standard_context, expected_namespace)
namespace = expected_namespace || project&.namespace
expect(standard_context).not_to eq(nil)
expect(standard_context[:data][:user_id]).to eq(user&.id)
expect(standard_context[:data][:namespace_id]).to eq(namespace&.id)
expect(standard_context[:data][:project_id]).to eq(project&.id)
end
def validate_service_ping_context(service_ping_context)
expect(service_ping_context).not_to eq(nil)
expect(service_ping_context[:data][:data_source]).to eq(:redis_hll)
expect(service_ping_context[:data][:event_name]).to eq(event_name)
end
let_it_be(:user) { build(:user, id: 1) }
let_it_be(:project) { build(:project, id: 2) }
let_it_be(:namespace) { project.namespace }
let_it_be(:project_namespace) { build(:namespace, id: 2) }
let_it_be(:project) { build(:project, id: 3, namespace: project_namespace) }
let(:redis) { instance_double('Redis') }
let(:fake_snowplow) { instance_double(Gitlab::Tracking::Destinations::Snowplow) }
let(:event_name) { 'g_edit_by_web_ide' }
let(:unique_property) { :user }
let(:unique_value) { user.id }
let(:redis_arguments) { [event_name, Date.today.strftime('%G-%V')] }
context 'when only user is passed' do
let(:project) { nil }
let(:namespace) { nil }
it 'updated all tracking methods' do
described_class.track_event(event_name, user: user)
expect_redis_tracking
expect_redis_hll_tracking
expect_snowplow_tracking
end
end
context 'when namespace is passed' do
let(:namespace) { build(:namespace, id: 4) }
it 'uses id from namespace' do
described_class.track_event(event_name, user: user, project: project, namespace: namespace)
expect_redis_tracking
expect_redis_hll_tracking
expect_snowplow_tracking(namespace)
end
end
context 'when namespace is not passed' do
let(:unique_property) { :namespace }
let(:unique_value) { project.namespace.id }
it 'uses id from projects namespace' do
described_class.track_event(event_name, user: user, project: project)
expect_redis_tracking
expect_redis_hll_tracking
expect_snowplow_tracking(project.namespace)
end
end
context 'when arguments are invalid' do
context 'when user is not an instance of User' do
let(:user) { 'a_string' }
it_behaves_like 'an event that logs an error' do
let(:event_kwargs) { { user: user, project: project } }
end
end
context 'when project is not an instance of Project' do
let(:project) { 42 }
it_behaves_like 'an event that logs an error' do
let(:event_kwargs) { { user: user, project: project } }
end
end
context 'when namespace is not an instance of Namespace' do
let(:namespace) { false }
it_behaves_like 'an event that logs an error' do
let(:event_kwargs) { { user: user, namespace: namespace } }
end
end
end
it 'updates Redis, RedisHLL and Snowplow', :aggregate_failures do
params = { user: user, project: project, namespace: namespace }
described_class.track_event(event_name, **params)
described_class.track_event(event_name, user: user, project: project)
expect_redis_tracking
expect_redis_hll_tracking(event_name)
expect_snowplow_tracking(event_name) # Add test for arguments
expect_redis_hll_tracking
expect_snowplow_tracking
end
it 'rescues error', :aggregate_failures do
params = { user: user, project: project, namespace: namespace }
params = { user: user, project: project }
error = StandardError.new("something went wrong")
allow(fake_snowplow).to receive(:event).and_raise(error)
@ -123,8 +226,8 @@ RSpec.describe Gitlab::InternalEvents, :snowplow, feature_category: :product_ana
described_class.track_event(event_name, user: user, project: project)
expect_redis_tracking
expect_redis_hll_tracking(event_name)
expect_snowplow_tracking(event_name)
expect_redis_hll_tracking
expect_snowplow_tracking
end
context 'when property is missing' do
@ -136,22 +239,12 @@ RSpec.describe Gitlab::InternalEvents, :snowplow, feature_category: :product_ana
end
end
context 'when method does not exist on property', :aggregate_failures do
it 'logs error on missing method' do
expect { described_class.track_event(event_name, project: "a_string") }.not_to raise_error
expect_redis_tracking
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
context 'when send_snowplow_event is false' do
it 'logs to Redis and RedisHLL but not Snowplow' do
described_class.track_event(event_name, send_snowplow_event: false, user: user, project: project)
expect_redis_tracking
expect_redis_hll_tracking(event_name)
expect_redis_hll_tracking
expect(fake_snowplow).not_to have_received(:event)
end
end
@ -170,7 +263,7 @@ RSpec.describe Gitlab::InternalEvents, :snowplow, feature_category: :product_ana
described_class.track_event(event_name, user: user, project: project)
expect_redis_tracking
expect_snowplow_tracking(event_name)
expect_snowplow_tracking(project.namespace)
expect(Gitlab::UsageDataCounters::HLLRedisCounter).not_to have_received(:track_event)
end
end

View File

@ -12,11 +12,10 @@ RSpec.describe Gitlab::Puma::ErrorHandler, feature_category: :shared do
describe '#execute' do
it 'captures the exception and returns a Rack response' do
allow(Raven.configuration).to receive(:capture_allowed?).and_return(true)
expect(Raven).to receive(:capture_exception).with(
expect(Gitlab::ErrorTracking).to receive(:track_exception).with(
ex,
tags: { handler: 'puma_low_level' },
extra: { puma_env: env, status_code: status_code }
{ puma_env: env, status_code: status_code },
{ handler: 'puma_low_level' }
).and_call_original
status, headers, message = subject.execute(ex, env, status_code)
@ -26,25 +25,10 @@ RSpec.describe Gitlab::Puma::ErrorHandler, feature_category: :shared do
expect(message).to eq(described_class::PROD_ERROR_MESSAGE)
end
context 'when capture is not allowed' do
it 'returns a Rack response without capturing the exception' do
allow(Raven.configuration).to receive(:capture_allowed?).and_return(false)
expect(Raven).not_to receive(:capture_exception)
status, headers, message = subject.execute(ex, env, status_code)
expect(status).to eq(500)
expect(headers).to eq({})
expect(message).to eq(described_class::PROD_ERROR_MESSAGE)
end
end
context 'when not in production' do
let(:is_production) { false }
it 'returns a Rack response with dev error message' do
allow(Raven.configuration).to receive(:capture_allowed?).and_return(true)
status, headers, message = subject.execute(ex, env, status_code)
expect(status).to eq(500)
@ -57,9 +41,6 @@ RSpec.describe Gitlab::Puma::ErrorHandler, feature_category: :shared do
let(:status_code) { 500 }
it 'defaults to error 500' do
allow(Raven.configuration).to receive(:capture_allowed?).and_return(false)
expect(Raven).not_to receive(:capture_exception)
status, headers, message = subject.execute(ex, env, status_code)
expect(status).to eq(500)
@ -72,8 +53,6 @@ RSpec.describe Gitlab::Puma::ErrorHandler, feature_category: :shared do
let(:status_code) { 404 }
it 'uses the provided status code in the response' do
allow(Raven.configuration).to receive(:capture_allowed?).and_return(true)
status, headers, message = subject.execute(ex, env, status_code)
expect(status).to eq(404)

View File

@ -115,6 +115,18 @@ RSpec.describe Event, feature_category: :user_profile do
end
end
describe '.for_merge_request' do
let(:mr_event) { create(:event, :for_merge_request, project: project) }
before do
create(:event, :for_issue, project: project)
end
it 'returns events for MergeRequest target_type' do
expect(described_class.for_merge_request).to contain_exactly(mr_event)
end
end
describe '.created_at' do
it 'can find the right event' do
time = 1.day.ago
@ -128,6 +140,21 @@ RSpec.describe Event, feature_category: :user_profile do
end
end
describe '.created_between' do
it 'returns events created between given timestamps' do
start_time = 2.days.ago
end_time = Date.today
create(:event, created_at: 3.days.ago)
e1 = create(:event, created_at: 2.days.ago)
e2 = create(:event, created_at: 1.day.ago)
found = described_class.created_between(start_time, end_time)
expect(found).to contain_exactly(e1, e2)
end
end
describe '.for_fingerprint' do
let_it_be(:with_fingerprint) { create(:event, fingerprint: 'aaa', project: project) }
@ -152,16 +179,28 @@ RSpec.describe Event, feature_category: :user_profile do
end
describe '.contributions' do
let!(:merge_request_event) { create(:event, :created, :for_merge_request, project: project) }
let!(:issue_event) { create(:event, :created, :for_issue, project: project) }
let!(:work_item_event) { create(:event, :created, :for_work_item, project: project) }
let!(:design_event) { create(:design_event, project: project) }
let!(:merge_request_events) do
%i[created closed merged approved].map do |action|
create(:event, :for_merge_request, action: action, project: project)
end
end
it 'returns events for MergeRequest, Issue and WorkItem' do
let!(:work_item_event) { create(:event, :created, :for_work_item, project: project) }
let!(:issue_events) do
%i[created closed].map { |action| create(:event, :for_issue, action: action, project: project) }
end
let!(:push_event) { create_push_event(project, project.owner) }
let!(:comment_event) { create(:event, :commented, project: project) }
before do
create(:design_event, project: project) # should not be in scope
end
it 'returns events for MergeRequest, Issue, WorkItem and push, comment events' do
expect(described_class.contributions).to contain_exactly(
merge_request_event,
issue_event,
work_item_event
*merge_request_events, *issue_events, work_item_event,
push_event, comment_event
)
end
end

View File

@ -35,17 +35,19 @@ RSpec.describe 'Query.runners', feature_category: :runner_fleet do
end
context 'with filters' do
shared_examples 'a working graphql query returning expected runner' do
shared_examples 'a working graphql query returning expected runners' do
it_behaves_like 'a working graphql query' do
before do
post_graphql(query, current_user: current_user)
end
end
it 'returns expected runner' do
it 'returns expected runners' do
post_graphql(query, current_user: current_user)
expect(runners_graphql_data['nodes']).to contain_exactly(a_graphql_entity_for(expected_runner))
expect(runners_graphql_data['nodes']).to contain_exactly(
*Array(expected_runners).map { |expected_runner| a_graphql_entity_for(expected_runner) }
)
end
it 'does not execute more queries per runner', :aggregate_failures do
@ -95,24 +97,36 @@ RSpec.describe 'Query.runners', feature_category: :runner_fleet do
let(:runner_type) { 'INSTANCE_TYPE' }
let(:status) { 'ACTIVE' }
let!(:expected_runner) { instance_runner }
let(:expected_runners) { instance_runner }
it_behaves_like 'a working graphql query returning expected runner'
it_behaves_like 'a working graphql query returning expected runners'
end
context 'runner_type is PROJECT_TYPE and status is NEVER_CONTACTED' do
let(:runner_type) { 'PROJECT_TYPE' }
let(:status) { 'NEVER_CONTACTED' }
let!(:expected_runner) { project_runner }
let(:expected_runners) { project_runner }
it_behaves_like 'a working graphql query returning expected runner'
it_behaves_like 'a working graphql query returning expected runners'
end
end
context 'when filtered on version prefix' do
let_it_be(:version_runner) { create(:ci_runner, :project, active: false, description: 'Runner with machine') }
let_it_be(:version_runner_machine) { create(:ci_runner_machine, runner: version_runner, version: '15.11.0') }
let_it_be(:runner_15_10_1) { create_ci_runner(version: '15.10.1') }
let_it_be(:runner_15_11_0) { create_ci_runner(version: '15.11.0') }
let_it_be(:runner_15_11_1) { create_ci_runner(version: '15.11.1') }
let_it_be(:runner_16_1_0) { create_ci_runner(version: '16.1.0') }
let(:fields) do
<<~QUERY
nodes {
id
}
QUERY
end
let(:query) do
%(
@ -124,12 +138,44 @@ RSpec.describe 'Query.runners', feature_category: :runner_fleet do
)
end
context 'version_prefix is "15."' do
context 'when version_prefix is "15."' do
let(:version_prefix) { '15.' }
let!(:expected_runner) { version_runner }
it_behaves_like 'a working graphql query returning expected runners' do
let(:expected_runners) { [runner_15_10_1, runner_15_11_0, runner_15_11_1] }
end
end
it_behaves_like 'a working graphql query returning expected runner'
context 'when version_prefix is "15.11."' do
let(:version_prefix) { '15.11.' }
it_behaves_like 'a working graphql query returning expected runners' do
let(:expected_runners) { [runner_15_11_0, runner_15_11_1] }
end
end
context 'when version_prefix is "15.11.0"' do
let(:version_prefix) { '15.11.0' }
it_behaves_like 'a working graphql query returning expected runners' do
let(:expected_runners) { runner_15_11_0 }
end
end
context 'when version_prefix is not digits' do
let(:version_prefix) { 'a.b' }
it_behaves_like 'a working graphql query returning expected runners' do
let(:expected_runners) do
[instance_runner, project_runner, runner_15_10_1, runner_15_11_0, runner_15_11_1, runner_16_1_0]
end
end
end
def create_ci_runner(args = {}, version:)
create(:ci_runner, :project, **args).tap do |runner|
create(:ci_runner_machine, runner: runner, version: version)
end
end
end
end

View File

@ -35,9 +35,9 @@ RSpec.describe API::Ml::Mlflow::ModelVersions, feature_category: :mlops do
response
end
describe 'GET /projects/:id/ml/mlflow/api/2.0/mlflow/model_versions/get' do
describe 'GET /projects/:id/ml/mlflow/api/2.0/mlflow/model-versions/get' do
let(:route) do
"/projects/#{project_id}/ml/mlflow/api/2.0/mlflow/model_versions/get?name=#{name}&version=#{version}"
"/projects/#{project_id}/ml/mlflow/api/2.0/mlflow/model-versions/get?name=#{name}&version=#{version}"
end
it 'returns the model version', :aggregate_failures do
@ -51,7 +51,7 @@ RSpec.describe API::Ml::Mlflow::ModelVersions, feature_category: :mlops do
context 'when has access' do
context 'and model name in incorrect' do
let(:route) do
"/projects/#{project_id}/ml/mlflow/api/2.0/mlflow/model_versions/get?name=--&version=#{version}"
"/projects/#{project_id}/ml/mlflow/api/2.0/mlflow/model-versions/get?name=--&version=#{version}"
end
it_behaves_like 'MLflow|Not Found - Resource Does Not Exist'
@ -59,7 +59,7 @@ RSpec.describe API::Ml::Mlflow::ModelVersions, feature_category: :mlops do
context 'and version in incorrect' do
let(:route) do
"/projects/#{project_id}/ml/mlflow/api/2.0/mlflow/model_versions/get?name=#{name}&version=--"
"/projects/#{project_id}/ml/mlflow/api/2.0/mlflow/model-versions/get?name=#{name}&version=--"
end
it_behaves_like 'MLflow|Not Found - Resource Does Not Exist'