Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
34a59635a9
commit
77ded523f1
|
|
@ -150,7 +150,7 @@ review-deploy-sample-projects:
|
|||
|
||||
.review-stop-base:
|
||||
extends: .review-workflow-base
|
||||
timeout: 15min
|
||||
timeout: 30min
|
||||
environment:
|
||||
action: stop
|
||||
variables:
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
};
|
||||
|
|
@ -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?
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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 GitLab’s 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 ""
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 } }
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
|
@ -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',
|
||||
}),
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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' } }
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
Loading…
Reference in New Issue