Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-11-16 15:07:22 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-11-16 15:07:22 +0300
commit77ded523f119396c72e4bcbcd008ff6b84134ef4 (patch)
treef9fc698ab476ab8b0e1d0b879ebc48560583fce3
parent34a59635a9f25499193aa71a2cae3581f9892cbb (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.gitlab/ci/review-apps/main.gitlab-ci.yml2
-rw-r--r--app/assets/javascripts/diffs/components/merge_conflict_warning.vue62
-rw-r--r--app/assets/javascripts/main.js2
-rw-r--r--app/assets/javascripts/pages/shared/nav/sidebar_tracking.js44
-rw-r--r--app/finders/projects_finder.rb6
-rw-r--r--app/models/event.rb25
-rw-r--r--app/models/project.rb2
-rw-r--r--app/models/user.rb2
-rw-r--r--config/feature_flags/development/contributions_calendar_refactoring.yml8
-rw-r--r--doc/ci/services/index.md7
-rw-r--r--doc/development/internal_analytics/internal_event_instrumentation/quick_start.md6
-rw-r--r--doc/subscriptions/self_managed/index.md25
-rw-r--r--lib/api/ml/mlflow/model_versions.rb2
-rw-r--r--lib/gitlab/contributions_calendar.rb101
-rw-r--r--lib/gitlab/error_tracking.rb16
-rw-r--r--lib/gitlab/error_tracking/context_payload_generator.rb22
-rw-r--r--lib/gitlab/i18n.rb16
-rw-r--r--lib/gitlab/internal_events.rb21
-rw-r--r--lib/gitlab/puma/error_handler.rb9
-rw-r--r--locale/gitlab.pot9
-rwxr-xr-xscripts/review_apps/review-apps.sh26
-rw-r--r--spec/finders/projects_finder_spec.rb13
-rw-r--r--spec/frontend/diffs/components/merge_conflict_warning_spec.js58
-rw-r--r--spec/frontend/pages/shared/nav/sidebar_tracking_spec.js160
-rw-r--r--spec/lib/gitlab/contributions_calendar_spec.rb163
-rw-r--r--spec/lib/gitlab/error_tracking/context_payload_generator_spec.rb25
-rw-r--r--spec/lib/gitlab/error_tracking_spec.rb37
-rw-r--r--spec/lib/gitlab/internal_events_spec.rb149
-rw-r--r--spec/lib/gitlab/puma/error_handler_spec.rb27
-rw-r--r--spec/models/event_spec.rb53
-rw-r--r--spec/requests/api/graphql/ci/runners_spec.rb70
-rw-r--r--spec/requests/api/ml/mlflow/model_versions_spec.rb8
32 files changed, 605 insertions, 571 deletions
diff --git a/.gitlab/ci/review-apps/main.gitlab-ci.yml b/.gitlab/ci/review-apps/main.gitlab-ci.yml
index dfcd65238ec..a1378629270 100644
--- a/.gitlab/ci/review-apps/main.gitlab-ci.yml
+++ b/.gitlab/ci/review-apps/main.gitlab-ci.yml
@@ -150,7 +150,7 @@ review-deploy-sample-projects:
.review-stop-base:
extends: .review-workflow-base
- timeout: 15min
+ timeout: 30min
environment:
action: stop
variables:
diff --git a/app/assets/javascripts/diffs/components/merge_conflict_warning.vue b/app/assets/javascripts/diffs/components/merge_conflict_warning.vue
deleted file mode 100644
index 6cb1ed4cbcf..00000000000
--- a/app/assets/javascripts/diffs/components/merge_conflict_warning.vue
+++ /dev/null
@@ -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>
diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js
index 29189e3ac2f..b41f306eabd 100644
--- a/app/assets/javascripts/main.js
+++ b/app/assets/javascripts/main.js
@@ -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();
diff --git a/app/assets/javascripts/pages/shared/nav/sidebar_tracking.js b/app/assets/javascripts/pages/shared/nav/sidebar_tracking.js
deleted file mode 100644
index 47aae36ecbb..00000000000
--- a/app/assets/javascripts/pages/shared/nav/sidebar_tracking.js
+++ /dev/null
@@ -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);
- });
-};
diff --git a/app/finders/projects_finder.rb b/app/finders/projects_finder.rb
index 1aa5245590e..2a781c037f6 100644
--- a/app/finders/projects_finder.rb
+++ b/app/finders/projects_finder.rb
@@ -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?
diff --git a/app/models/event.rb b/app/models/event.rb
index 7de7ad8ccd6..3ff5a46f2d9 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -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
diff --git a/app/models/project.rb b/app/models/project.rb
index 4aec4207ece..37e86df9b7c 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -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,
diff --git a/app/models/user.rb b/app/models/user.rb
index fbdaa110435..109b0506648 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -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)
diff --git a/config/feature_flags/development/contributions_calendar_refactoring.yml b/config/feature_flags/development/contributions_calendar_refactoring.yml
new file mode 100644
index 00000000000..e779939cc28
--- /dev/null
+++ b/config/feature_flags/development/contributions_calendar_refactoring.yml
@@ -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
diff --git a/doc/ci/services/index.md b/doc/ci/services/index.md
index d0481482d00..42528effb46 100644
--- a/doc/ci/services/index.md
+++ b/doc/ci/services/index.md
@@ -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
diff --git a/doc/development/internal_analytics/internal_event_instrumentation/quick_start.md b/doc/development/internal_analytics/internal_event_instrumentation/quick_start.md
index 79323263b07..fc146ab884d 100644
--- a/doc/development/internal_analytics/internal_event_instrumentation/quick_start.md
+++ b/doc/development/internal_analytics/internal_event_instrumentation/quick_start.md
@@ -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
diff --git a/doc/subscriptions/self_managed/index.md b/doc/subscriptions/self_managed/index.md
index 745e1f63e85..563cafd0c29 100644
--- a/doc/subscriptions/self_managed/index.md
+++ b/doc/subscriptions/self_managed/index.md
@@ -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.
diff --git a/lib/api/ml/mlflow/model_versions.rb b/lib/api/ml/mlflow/model_versions.rb
index 989b79e5774..04ada5204fd 100644
--- a/lib/api/ml/mlflow/model_versions.rb
+++ b/lib/api/ml/mlflow/model_versions.rb
@@ -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'
diff --git a/lib/gitlab/contributions_calendar.rb b/lib/gitlab/contributions_calendar.rb
index 2068a9ae7d5..c91fdd59c57 100644
--- a/lib/gitlab/contributions_calendar.rb
+++ b/lib/gitlab/contributions_calendar.rb
@@ -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
+ # rubocop: enable CodeReuse/ActiveRecord
end
- # rubocop: enable CodeReuse/ActiveRecord
- def starting_year
- @contributor_time_instance.years_ago(1).year
- end
+ private
- def starting_month
- @contributor_time_instance.month
+ 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
- private
-
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
diff --git a/lib/gitlab/error_tracking.rb b/lib/gitlab/error_tracking.rb
index 2b00fe48951..239aee97378 100644
--- a/lib/gitlab/error_tracking.rb
+++ b/lib/gitlab/error_tracking.rb
@@ -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)
diff --git a/lib/gitlab/error_tracking/context_payload_generator.rb b/lib/gitlab/error_tracking/context_payload_generator.rb
index 3d0a707608f..23dd2e33a58 100644
--- a/lib/gitlab/error_tracking/context_payload_generator.rb
+++ b/lib/gitlab/error_tracking/context_payload_generator.rb
@@ -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
diff --git a/lib/gitlab/i18n.rb b/lib/gitlab/i18n.rb
index 96e3d90c139..02afdedb4be 100644
--- a/lib/gitlab/i18n.rb
+++ b/lib/gitlab/i18n.rb
@@ -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
diff --git a/lib/gitlab/internal_events.rb b/lib/gitlab/internal_events.rb
index a8f28615cd3..c775182fb40 100644
--- a/lib/gitlab/internal_events.rb
+++ b/lib/gitlab/internal_events.rb
@@ -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)
diff --git a/lib/gitlab/puma/error_handler.rb b/lib/gitlab/puma/error_handler.rb
index 4efc4866431..9eabe0731e2 100644
--- a/lib/gitlab/puma/error_handler.rb
+++ b/lib/gitlab/puma/error_handler.rb
@@ -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]
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index a4b94710133..ef6c1150fe2 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -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 ""
diff --git a/scripts/review_apps/review-apps.sh b/scripts/review_apps/review-apps.sh
index ab1675871ee..523087671ed 100755
--- a/scripts/review_apps/review-apps.sh
+++ b/scripts/review_apps/review-apps.sh
@@ -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
}
diff --git a/spec/finders/projects_finder_spec.rb b/spec/finders/projects_finder_spec.rb
index f7afd96fa09..e570b49e1da 100644
--- a/spec/finders/projects_finder_spec.rb
+++ b/spec/finders/projects_finder_spec.rb
@@ -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 } }
diff --git a/spec/frontend/diffs/components/merge_conflict_warning_spec.js b/spec/frontend/diffs/components/merge_conflict_warning_spec.js
deleted file mode 100644
index 715912b361f..00000000000
--- a/spec/frontend/diffs/components/merge_conflict_warning_spec.js
+++ /dev/null
@@ -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);
- },
- );
-});
diff --git a/spec/frontend/pages/shared/nav/sidebar_tracking_spec.js b/spec/frontend/pages/shared/nav/sidebar_tracking_spec.js
deleted file mode 100644
index 04f53e048ed..00000000000
--- a/spec/frontend/pages/shared/nav/sidebar_tracking_spec.js
+++ /dev/null
@@ -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',
- }),
- });
- });
- });
-});
diff --git a/spec/lib/gitlab/contributions_calendar_spec.rb b/spec/lib/gitlab/contributions_calendar_spec.rb
index 326e27fa716..732b7cb3e59 100644
--- a/spec/lib/gitlab/contributions_calendar_spec.rb
+++ b/spec/lib/gitlab/contributions_calendar_spec.rb
@@ -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)
+ 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)
- # 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)
+ # 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
- context "when the user has opted-in for private contributions" do
- before do
- contributor.update_column(:include_private_contributions, true)
- 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)
+ 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
+ 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)
- # 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)
- 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
- 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)
+ it "does not fail if there are no contributed projects" do
+ expect(calendar.activity_dates[today]).to eq(nil)
+ end
end
+ end
- it "does not fail if there are no contributed projects" do
- expect(calendar.activity_dates[today]).to eq(nil)
+ 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,65 +219,56 @@ 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(contributor).events_by_date(today)).to contain_exactly(e1, e2, e3)
- 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)
+ expect(calendar(contributor).events_by_date(today)).to contain_exactly(e1, e2, e3)
end
- context 'when the user cannot read cross project' do
+ context 'when contributions_calendar_refactoring feature flag is disabled' do
before do
- allow(Ability).to receive(:allowed?).and_call_original
- expect(Ability).to receive(:allowed?).with(user, :read_cross_project) { false }
+ stub_feature_flags(contributions_calendar_refactoring: false)
end
- it 'does not return any events' do
- create_event(public_project, today)
+ 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(user).events_by_date(today)).to be_empty
+ 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
- end
- describe '#starting_year' do
- let(:travel_time) { Time.find_zone('UTC').local(2020, 12, 31, 19, 0, 0) }
+ it "includes diff notes on merge request" do
+ e1 = create_event(public_project, today, 0, :commented, :diff_note_on_merge_request)
- 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
+ expect(calendar.events_by_date(today)).to contain_exactly(e1)
end
- context "when the contributor's timezone is set to Sydney" do
- let(:contributor) { create(:user, { timezone: 'Sydney' }) }
+ 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)
- it "is the start of last year in Sydney" do
- expect(calendar.starting_year).to eq(2020)
- end
+ expect(calendar.events_by_date(today)).to contain_exactly(
+ mr_created_event, mr_closed_event, mr_approved_event, mr_merged_event
+ )
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)
+ context 'when the user cannot read cross project' do
+ before do
+ allow(Ability).to receive(:allowed?).and_call_original
+ expect(Ability).to receive(:allowed?).with(user, :read_cross_project) { false }
end
- end
- context "when the contributor's timezone is set to Sydney" do
- let(:contributor) { create(:user, { timezone: 'Sydney' }) }
+ it 'does not return any events' do
+ create_event(public_project, today)
- it "is the start of this month in Sydney" do
- expect(calendar.starting_month).to eq(1)
+ expect(calendar(user).events_by_date(today)).to be_empty
end
end
end
diff --git a/spec/lib/gitlab/error_tracking/context_payload_generator_spec.rb b/spec/lib/gitlab/error_tracking/context_payload_generator_spec.rb
index 38745fe0cde..932c1b2fb4c 100644
--- a/spec/lib/gitlab/error_tracking/context_payload_generator_spec.rb
+++ b/spec/lib/gitlab/error_tracking/context_payload_generator_spec.rb
@@ -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
diff --git a/spec/lib/gitlab/error_tracking_spec.rb b/spec/lib/gitlab/error_tracking_spec.rb
index 79016335a40..c9b2e21d934 100644
--- a/spec/lib/gitlab/error_tracking_spec.rb
+++ b/spec/lib/gitlab/error_tracking_spec.rb
@@ -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' } }
diff --git a/spec/lib/gitlab/internal_events_spec.rb b/spec/lib/gitlab/internal_events_spec.rb
index 1cad9e7e5cf..68d6f3700af 100644
--- a/spec/lib/gitlab/internal_events_spec.rb
+++ b/spec/lib/gitlab/internal_events_spec.rb
@@ -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)
+
+ 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)
- expect(fake_snowplow).to have_received(:event)
- .with('InternalEventTracking', event_name, context: contexts)
+ # 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
diff --git a/spec/lib/gitlab/puma/error_handler_spec.rb b/spec/lib/gitlab/puma/error_handler_spec.rb
index 5b7cdf37af1..bfcbf32e899 100644
--- a/spec/lib/gitlab/puma/error_handler_spec.rb
+++ b/spec/lib/gitlab/puma/error_handler_spec.rb
@@ -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)
diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb
index 4c2d44a3e12..6430fc2ffc8 100644
--- a/spec/models/event_spec.rb
+++ b/spec/models/event_spec.rb
@@ -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!(:merge_request_events) do
+ %i[created closed merged approved].map do |action|
+ create(:event, :for_merge_request, action: action, project: project)
+ end
+ end
+
let!(:work_item_event) { create(:event, :created, :for_work_item, project: project) }
- let!(:design_event) { create(:design_event, 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 and WorkItem' do
+ 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
diff --git a/spec/requests/api/graphql/ci/runners_spec.rb b/spec/requests/api/graphql/ci/runners_spec.rb
index 0e2712d742d..9b45e16178a 100644
--- a/spec/requests/api/graphql/ci/runners_spec.rb
+++ b/spec/requests/api/graphql/ci/runners_spec.rb
@@ -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
+
+ context 'when version_prefix is "15.11."' do
+ let(:version_prefix) { '15.11.' }
- it_behaves_like 'a working graphql query returning expected runner'
+ 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
diff --git a/spec/requests/api/ml/mlflow/model_versions_spec.rb b/spec/requests/api/ml/mlflow/model_versions_spec.rb
index f59888ec70f..9813ed95ab3 100644
--- a/spec/requests/api/ml/mlflow/model_versions_spec.rb
+++ b/spec/requests/api/ml/mlflow/model_versions_spec.rb
@@ -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'