Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2023-07-24 09:10:04 +00:00
parent 02f08e9e78
commit fd7c75bf60
38 changed files with 529 additions and 75 deletions

View File

@ -497,5 +497,5 @@
.use-kube-context:
before_script:
- export KUBE_CONTEXT="gitlab-org/gitlab:review-apps"
- export KUBE_CONTEXT="${CI_PROJECT_NAMESPACE}/gitlab:review-apps"
- kubectl config use-context ${KUBE_CONTEXT}

View File

@ -1,5 +1,4 @@
@import 'mixins_and_variables_and_functions';
@import 'framework/buttons';
$tabs-holder-z-index: 250;
$comparison-empty-state-height: 62px;
@ -34,27 +33,6 @@ $comparison-empty-state-height: 62px;
}
}
.mr-state-widget {
.accept-merge-holder {
.accept-action {
.accept-merge-request {
&.ci-preparing,
&.ci-pending,
&.ci-running {
@include btn-blue;
}
&.ci-skipped,
&.ci-failed,
&.ci-canceled,
&.ci-error {
@include btn-red;
}
}
}
}
}
.mr_source_commit,
.mr_target_commit {
margin-bottom: 0;
@ -201,8 +179,8 @@ $comparison-empty-state-height: 62px;
.table-holder {
.ci-table {
th {
background-color: $white;
color: $gl-text-color-secondary;
background-color: var(--white, $white);
color: var(--gl-gray-700, $gl-text-color-secondary);
}
}
}
@ -210,8 +188,8 @@ $comparison-empty-state-height: 62px;
.merge-request-tabs-holder {
top: $calc-application-header-height;
z-index: $tabs-holder-z-index;
background-color: $body-bg;
border-bottom: 1px solid $border-color;
background-color: var(--gray-10, $body-bg);
border-bottom: 1px solid var(--border-color, $border-color);
@include media-breakpoint-up(md) {
position: sticky;
@ -238,7 +216,7 @@ $comparison-empty-state-height: 62px;
margin-right: auto;
.inner-page-scroll-tabs {
background-color: $white;
background-color: var(--white, $white);
margin-left: -$gl-padding;
padding-left: $gl-padding;
}
@ -307,7 +285,7 @@ $comparison-empty-state-height: 62px;
opacity: 0.65;
&:hover {
color: $gray-500;
color: var(--gray-500, $gray-500);
text-decoration: none;
}
}

View File

@ -22,6 +22,14 @@ class DeviseMailer < Devise::Mailer
super
end
def email_changed(record, opts = {})
if Gitlab.com?
devise_mail(record, :email_changed_gitlab_com, opts)
else
devise_mail(record, :email_changed, opts)
end
end
protected
def subject_for(key)

View File

@ -19,17 +19,16 @@ module Emails
end
def setup_review_email(review_id, recipient_id)
review = Review.find_by_id(review_id)
@notes = review.notes
@discussions = Discussion.build_discussions(review.discussion_ids, preload_note_diff_file: true)
@review = Review.find_by_id(review_id)
@notes = @review.notes
@discussions = Discussion.build_discussions(@review.discussion_ids, preload_note_diff_file: true)
@include_diff_discussion_stylesheet = @discussions.values.any? do |discussion|
discussion.diff_discussion? && discussion.on_text?
end
@author = review.author
@author_name = review.author_name
@project = review.project
@merge_request = review.merge_request
@author = @review.author
@author_name = @review.author_name
@project = @review.project
@merge_request = @review.merge_request
@target_url = project_merge_request_url(@project, @merge_request)
@sent_notification = SentNotification.record(@merge_request, recipient_id, reply_key)
end

View File

@ -28,6 +28,10 @@ class DeviseMailerPreview < ActionMailer::Preview
DeviseMailer.user_admin_approval(unsaved_user, {})
end
def email_changed
DeviseMailer.email_changed(unsaved_user, {})
end
private
def unsaved_user

View File

@ -284,6 +284,13 @@ class NotifyPreview < ActionMailer::Preview
Notify.request_review_merge_request_email(user.id, merge_request.id, user.id).message
end
def new_review_email
review = Review.last
mr_author = review.merge_request.author
Notify.new_review_email(mr_author.id, review.id).message
end
def project_was_moved_email
Notify.project_was_moved_email(project.id, user.id, "gitlab/gitlab").message
end

View File

@ -67,7 +67,7 @@ class Deployment < ApplicationRecord
state_machine :status, initial: :created do
event :run do
transition created: :running
transition [:created, :blocked] => :running
end
event :block do
@ -79,6 +79,7 @@ class Deployment < ApplicationRecord
transition skipped: :created
end
# Deprecated. To be removed when we remove `track_manual_deployments` feature flag.
event :unblock do
transition blocked: :created
end
@ -403,10 +404,16 @@ class Deployment < ApplicationRecord
end
def sync_status_with(build)
return false unless ::Deployment.statuses.include?(build.status)
return false if build.status == self.status
build_status = build.status
update_status!(build.status)
if ::Feature.enabled?(:track_manual_deployments, build.project)
build_status = 'blocked' if build_status == 'manual' # rubocop:disable Style/SoleNestedConditional
end
return false unless ::Deployment.statuses.include?(build_status)
return false if build_status == self.status
update_status!(build_status)
rescue StandardError => e
Gitlab::ErrorTracking.track_exception(
StatusSyncError.new(e.message), deployment_id: self.id, build_id: build.id)

View File

@ -59,6 +59,10 @@ module Ml
numeric?(iid)
end
def find_or_create(project, name, user)
create_with(user: user).find_or_create_by(project: project, name: name)
end
private
def numeric?(value)

View File

@ -21,5 +21,10 @@ module Ml
errors.add(:default_experiment) unless default_experiment.name == name
errors.add(:default_experiment) unless default_experiment.project_id == project_id
end
def self.find_or_create(project, name, experiment)
create_with(default_experiment: experiment)
.find_or_create_by(project: project, name: name)
end
end
end

View File

@ -18,6 +18,12 @@ module Ml
delegate :name, to: :model
class << self
def find_or_create(model, version, package)
create_with(package: package).find_or_create_by(project: model.project, model: model, version: version)
end
end
private
def valid_model?

View File

@ -32,3 +32,5 @@ class Review < ApplicationRecord
merge_request.user_mentions.where.not(note_id: nil)
end
end
Review.prepend_mod

View File

@ -52,3 +52,5 @@ module Projects
end
end
end
Projects::ImportExport::ProjectExportPresenter.prepend_mod_with('Projects::ImportExport::ProjectExportPresenter')

View File

@ -0,0 +1,19 @@
# frozen_string_literal: true
module Ml
class FindOrCreateExperimentService
def initialize(project, experiment_name, user = nil)
@project = project
@name = experiment_name
@user = user
end
def execute
Ml::Experiment.find_or_create(project, name, user)
end
private
attr_reader :project, :name, :user
end
end

View File

@ -0,0 +1,22 @@
# frozen_string_literal: true
module Ml
class FindOrCreateModelService
def initialize(project, model_name)
@project = project
@name = model_name
end
def execute
Ml::Model.find_or_create(
project,
name,
Ml::FindOrCreateExperimentService.new(project, name).execute
)
end
private
attr_reader :name, :project
end
end

View File

@ -0,0 +1,22 @@
# frozen_string_literal: true
module Ml
class FindOrCreateModelVersionService
def initialize(project, params = {})
@project = project
@name = params[:model_name]
@version = params[:version]
@package = params[:package]
end
def execute
model = Ml::FindOrCreateModelService.new(project, name).execute
Ml::ModelVersion.find_or_create(model, version, package)
end
private
attr_reader :version, :name, :project, :package
end
end

View File

@ -723,9 +723,12 @@ class NotificationService
# Notify users on new review in system
def new_review(review)
recipients = NotificationRecipients::BuildService.build_new_review_recipients(review)
deliver_options = new_review_deliver_options(review)
recipients.each do |recipient|
mailer.new_review_email(recipient.user.id, review.id).deliver_later
mailer
.new_review_email(recipient.user.id, review.id)
.deliver_later(deliver_options)
end
end
@ -946,6 +949,11 @@ class NotificationService
def warn_skipping_notifications(user, object)
Gitlab::AppLogger.warn(message: "Skipping sending notifications", user: user.id, klass: object.class.to_s, object_id: object.id)
end
def new_review_deliver_options(review)
# Overridden in EE
{}
end
end
NotificationService.prepend_mod_with('NotificationService')

View File

@ -0,0 +1,11 @@
= email_default_heading("Hello, #{@resource.name}!")
- if @resource.try(:unconfirmed_email?)
%p
We're contacting you to notify you that your email is being changed to #{@resource.reset.unconfirmed_email}.
- else
%p
We're contacting you to notify you that your email has been changed to #{@resource.email}.
%p
If you did not initiate this change, please contact your group owner immediately. If you have a Premium or Ultimate tier subscription, you can also contact GitLab support.

View File

@ -0,0 +1,9 @@
Hello, <%= @resource.name %>!
<% if @resource.try(:unconfirmed_email?) %>
We're contacting you to notify you that your email is being changed to <%= @resource.reset.unconfirmed_email %>.
<% else %>
We're contacting you to notify you that your email has been changed to <%= @resource.email %>.
<% end %>
If you did not initiate this change, please contact your group owner immediately. If you have a Premium or Ultimate tier subscription, you can also contact GitLab support.

View File

@ -22,3 +22,4 @@
- discussion.first_note.project = @project if discussion&.first_note
- target_url = project_merge_request_url(@project, @merge_request, anchor: "note_#{note.id}")
= render 'note_email', note: note, diff_limit: 3, target_url: target_url, note_style: "border-bottom:1px solid #ededed; padding-bottom: 1em;", include_stylesheet_link: false, discussion: discussion, author: @author
= render_if_exists 'notify/review_summary'

View File

@ -12,3 +12,5 @@
--
<% end %>
<% end %>
<%= render_if_exists 'notify/review_summary' %>

View File

@ -0,0 +1,8 @@
---
name: track_manual_deployments
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/125659
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/419039
milestone: '16.3'
type: development
group: group::environments
default_enabled: false

View File

@ -0,0 +1,11 @@
- title: "Deprecate field `hasSolutions` from GraphQL VulnerabilityType"
removal_milestone: "17.0"
announcement_milestone: "16.3"
breaking_change: true
reporter: thiagocsf
stage: Govern
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/414895
body: | # (required) Do not modify this line, instead modify the lines below.
The GraphQL field `Vulnerability.hasSolutions` is deprecated and will be removed in GitLab 17.0.
Use `Vulnerability.hasRemediations` instead.
documentation_url: https://docs.gitlab.com/ee/api/graphql/reference/#vulnerability

View File

@ -182,8 +182,7 @@ To enable advanced search:
[license](../../administration/license.md).
1. Configure the [advanced search settings](#advanced-search-configuration) for
your Elasticsearch cluster. Do not enable **Search with Elasticsearch enabled**
yet.
your Elasticsearch cluster. Do not select the **Search with Elasticsearch enabled** checkbox yet.
1. Index all data with a Rake task. The task creates an empty index if one does not already exist and
enables Elasticsearch indexing if the indexing is not already enabled:
@ -202,7 +201,7 @@ To enable advanced search:
1. On the Sidekiq dashboard, select **Queues** and wait for the `elastic_commit_indexer`
and `elastic_wiki_indexer` queues to drop to `0`.
These queues contain jobs to index code and wiki data for groups and projects.
1. After indexing completes, enable **Search with Elasticsearch enabled** and select **Save changes**.
1. After the indexing is complete, select the **Search with Elasticsearch enabled** checkbox, then select **Save changes**.
NOTE:
When your Elasticsearch cluster is down while Elasticsearch is enabled,
@ -221,7 +220,7 @@ To enable advanced search with **Index all projects**:
1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
1. Select **Admin Area**.
1. On the left sidebar, select **Settings > Advanced Search**.
1. Enable **Elasticsearch indexing** and select **Save changes**.
1. Select the **Elasticsearch indexing** checkbox, then select **Save changes**.
1. Select **Index all projects**.
1. Optional. Select **Check progress** to see the status of background jobs.
@ -382,14 +381,12 @@ The index pattern `*` requires a few permissions for advanced search to work.
### Limit the number of namespaces and projects that can be indexed
If you check checkbox `Limit the number of namespaces and projects that can be indexed`
under **Elasticsearch indexing restrictions** more options become available.
When you select the **Limit the number of namespaces and projects that can be indexed**
checkbox, you can specify namespaces and projects to index. If the namespace is a group,
any subgroups and projects belonging to those subgroups are also indexed.
![limit namespaces and projects options](img/limit_namespaces_projects_options.png)
You can select namespaces and projects to index exclusively. If the namespace is a group, it includes
any subgroups and projects belonging to those subgroups to be indexed as well.
Advanced search only provides cross-group code/commit search (global) if all name-spaces are indexed. In this particular scenario where only a subset of namespaces are indexed, a global search does not provide a code or commit scope. This is possible only in the scope of an indexed namespace. There is no way to code/commit search in multiple indexed namespaces (when only a subset of namespaces has been indexed). For example if two groups are indexed, there is no way to run a single code search on both. You can only run a code search on the first group and then on the second.
You can filter the selection dropdown list by writing part of the namespace or project name you're interested in.
@ -792,7 +789,7 @@ Make sure to prepare for this task by having a
bundle exec rake gitlab:elastic:clear_index_status RAILS_ENV=production
```
1. [Enable **Elasticsearch indexing**](#enable-advanced-search).
1. [Select the **Elasticsearch indexing** checkbox](#enable-advanced-search).
1. Indexing large Git repositories can take a while. To speed up the process, you can [tune for indexing speed](https://www.elastic.co/guide/en/elasticsearch/reference/current/tune-for-indexing-speed.html#tune-for-indexing-speed):
- You can temporarily disable [`refresh`](https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-refresh.html), the operation responsible for making changes to an index available to search.
@ -915,7 +912,7 @@ Make sure to prepare for this task by having a
} }'
```
1. After the indexing has completed, enable [**Search with Elasticsearch enabled**](#enable-advanced-search).
1. After the indexing is complete, [select the **Search with Elasticsearch enabled** checkbox](#enable-advanced-search).
### Deleted documents

View File

@ -281,12 +281,12 @@ queue, or the index is somehow in a state where migrations just cannot
proceed. It is always best to try to troubleshoot the root cause of the problem
by [viewing the logs](#view-logs).
If there are no other options, then you always have the option of recreating the
entire index from scratch. If you have a small GitLab installation, this can
sometimes be a quick way to resolve a problem, but if you have a large GitLab
installation, then this might take a very long time to complete. Until the
index is fully recreated, your index does not serve correct search results,
so you may want to disable **Search with Elasticsearch** while it is running.
As a last resort, you can recreate the index from scratch. For small GitLab installations,
recreating the index can be a quick way to resolve some issues. For large GitLab
installations, however, this method might take a very long time. Your index
does not show correct search results until the indexing is complete. You might
want to clear the **Search with Elasticsearch enabled** checkbox
while the indexing is running.
If you are sure you've read the above caveats and want to proceed, then you
should run the following Rake task to recreate the entire index from scratch:

View File

@ -174,6 +174,21 @@ The message field was removed from security reports schema in GitLab 16.0 and is
</div>
<div class="deprecation breaking-change" data-milestone="17.0">
### Deprecate field `hasSolutions` from GraphQL VulnerabilityType
<div class="deprecation-notes">
- Announced in GitLab <span class="milestone">16.3</span>
- Removal in GitLab <span class="milestone">17.0</span> ([breaking change](https://docs.gitlab.com/ee/update/terminology.html#breaking-change))
- To discuss this change or learn more, see the [deprecation issue](https://gitlab.com/gitlab-org/gitlab/-/issues/414895).
</div>
The GraphQL field `Vulnerability.hasSolutions` is deprecated and will be removed in GitLab 17.0.
Use `Vulnerability.hasRemediations` instead.
</div>
<div class="deprecation " data-milestone="17.0">
### Deprecate legacy shell escaping and quoting runner shell executor

View File

@ -6,6 +6,7 @@ module API
expose :id, documentation: { type: :string, example: "1234" }
expose :job_class_name, documentation: { type: :string, example: "CopyColumnUsingBackgroundMigrationJob" }
expose :table_name, documentation: { type: :string, example: "events" }
expose :column_name, documentation: { type: :string, example: "id" }
expose :status_name, as: :status, override: true, documentation: { type: :string, example: "active" }
expose :progress, documentation: { type: :float, example: 50 }
expose :created_at, documentation: { type: :dateTime, example: "2022-11-28T16:26:39+02:00" }

View File

@ -45170,6 +45170,9 @@ msgstr ""
msgid "Summary generated by AI"
msgstr ""
msgid "Summary generated by AI (Experiment)"
msgstr ""
msgid "Summary will be generated with the next push to this merge request and will appear here."
msgstr ""

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
module QA
RSpec.describe 'Manage', :requires_admin, :skip_live_env, product_group: :authentication_and_authorization do
RSpec.describe 'Manage', :requires_admin, product_group: :authentication_and_authorization do
describe '2FA' do
let(:admin_api_client) { Runtime::API::Client.as_admin }
let(:owner_api_client) { Runtime::API::Client.new(:gitlab, user: owner_user) }
@ -14,6 +14,7 @@ module QA
end
let(:sandbox_group) do
Flow::Login.sign_in(as: owner_user)
Resource::Sandbox.fabricate! do |sandbox_group|
sandbox_group.path = "gitlab-qa-2fa-sandbox-group-#{SecureRandom.hex(8)}"
sandbox_group.api_client = owner_api_client
@ -43,12 +44,7 @@ module QA
it(
'allows enforcing 2FA via UI and logging in with 2FA',
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347931',
quarantine: {
type: :bug,
only: { condition: -> { !QA::Runtime::Env.super_sidebar_enabled? } },
issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/409336'
}
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347931'
) do
enforce_two_factor_authentication_on_group(group)

View File

@ -150,10 +150,12 @@ RSpec.describe DeviseMailer do
end
describe '#email_changed' do
subject { described_class.email_changed(user, {}) }
let(:content_saas) { 'If you did not initiate this change, please contact your group owner immediately. If you have a Premium or Ultimate tier subscription, you can also contact GitLab support.' }
let(:content_self_managed) { 'If you did not initiate this change, please contact your administrator immediately.' }
let_it_be(:user) { create(:user) }
subject { described_class.email_changed(user, {}) }
it_behaves_like 'an email sent from GitLab'
it 'is sent to the user' do
@ -168,6 +170,18 @@ RSpec.describe DeviseMailer do
is_expected.to have_body_text /Hello, #{user.name}!/
end
context 'when self-managed' do
it 'has the expected content of self managed instance' do
is_expected.to have_body_text content_self_managed
end
end
context 'when saas', :saas do
it 'has the expected content of saas instance' do
is_expected.to have_body_text content_saas
end
end
context "email contains updated id" do
before do
user.update!(email: "new_email@test.com")

View File

@ -13,6 +13,7 @@ RSpec.describe 'Mailer previews' do
let_it_be(:issue) { create(:issue, project: project, milestone: milestone) }
let_it_be(:remote_mirror) { create(:remote_mirror, project: project) }
let_it_be(:member) { create(:project_member, :maintainer, project: project, created_by: user) }
let_it_be(:review) { create(:review, project: project, merge_request: merge_request, author: user) }
Gitlab.ee do
let_it_be(:epic) { create(:epic, group: group) }

View File

@ -1215,12 +1215,14 @@ RSpec.describe Deployment, feature_category: :continuous_delivery do
let(:ci_build) { create(:ci_build, project: project, status: build_status) }
shared_examples_for 'synchronizing deployment' do
let(:expected_deployment_status) { build_status.to_s }
it 'changes deployment status' do
expect(Gitlab::ErrorTracking).not_to receive(:track_exception)
is_expected.to eq(true)
expect(deployment.status).to eq(build_status.to_s)
expect(deployment.status).to eq(expected_deployment_status)
expect(deployment.errors).to be_empty
end
end
@ -1259,6 +1261,22 @@ RSpec.describe Deployment, feature_category: :continuous_delivery do
it_behaves_like 'ignoring build'
end
context 'with manual build' do
let(:build_status) { :manual }
it_behaves_like 'synchronizing deployment' do
let(:expected_deployment_status) { 'blocked' }
end
context 'when track_manual_deployments feature flag is disabled' do
before do
stub_feature_flags(track_manual_deployments: false)
end
it_behaves_like 'ignoring build'
end
end
context 'with running build' do
let(:build_status) { :running }
@ -1289,6 +1307,22 @@ RSpec.describe Deployment, feature_category: :continuous_delivery do
end
end
context 'with manual build' do
let(:build_status) { :manual }
it_behaves_like 'gracefully handling error' do
let(:error_message) { %{Status cannot transition via \"block\"} }
end
context 'when track_manual_deployments feature flag is disabled' do
before do
stub_feature_flags(track_manual_deployments: false)
end
it_behaves_like 'ignoring build'
end
end
context 'with running build' do
let(:build_status) { :running }
@ -1319,6 +1353,22 @@ RSpec.describe Deployment, feature_category: :continuous_delivery do
end
end
context 'with manual build' do
let(:build_status) { :manual }
it_behaves_like 'gracefully handling error' do
let(:error_message) { %{Status cannot transition via \"block\"} }
end
context 'when track_manual_deployments feature flag is disabled' do
before do
stub_feature_flags(track_manual_deployments: false)
end
it_behaves_like 'ignoring build'
end
end
context 'with running build' do
let(:build_status) { :running }

View File

@ -79,6 +79,45 @@ RSpec.describe Ml::Experiment, feature_category: :mlops do
end
end
describe '.find_or_create' do
let(:name) { exp.name }
let(:project) { exp.project }
subject(:find_or_create) { described_class.find_or_create(project, name, exp.user) }
context 'when experiments exists' do
it 'fetches existing experiment', :aggregate_failures do
expect { find_or_create }.not_to change { Ml::Experiment.count }
expect(find_or_create).to eq(exp)
end
end
context 'when experiments does not exist' do
let(:name) { 'a new experiment' }
it 'creates the experiment', :aggregate_failures do
expect { find_or_create }.to change { Ml::Experiment.count }.by(1)
expect(find_or_create.name).to eq(name)
expect(find_or_create.user).to eq(exp.user)
expect(find_or_create.project).to eq(project)
end
end
context 'when experiment name exists but project is different' do
let(:project) { create(:project) }
it 'creates a model', :aggregate_failures do
expect { find_or_create }.to change { Ml::Experiment.count }.by(1)
expect(find_or_create.name).to eq(name)
expect(find_or_create.user).to eq(exp.user)
expect(find_or_create.project).to eq(project)
end
end
end
describe '#with_candidate_count' do
let_it_be(:exp3) do
create(:ml_experiments, project: exp.project).tap do |e|

View File

@ -3,6 +3,13 @@
require 'spec_helper'
RSpec.describe Ml::Model, feature_category: :mlops do
let_it_be(:base_project) { create(:project) }
let_it_be(:existing_model) { create(:ml_models, name: 'an_existing_model', project: base_project) }
let_it_be(:valid_name) { 'a_valid_name' }
let_it_be(:default_experiment) { create(:ml_experiments, name: valid_name, project: base_project) }
let(:project) { base_project }
describe 'associations' do
it { is_expected.to belong_to(:project) }
it { is_expected.to have_one(:default_experiment) }
@ -12,11 +19,6 @@ RSpec.describe Ml::Model, feature_category: :mlops do
describe '#valid?' do
using RSpec::Parameterized::TableSyntax
let_it_be(:project) { create(:project) }
let_it_be(:existing_model) { create(:ml_models, name: 'an_existing_model', project: project) }
let_it_be(:valid_name) { 'a_valid_name' }
let_it_be(:default_experiment) { create(:ml_experiments, name: valid_name, project: project) }
let(:name) { valid_name }
subject(:errors) do
@ -59,4 +61,46 @@ RSpec.describe Ml::Model, feature_category: :mlops do
end
end
end
describe '.find_or_create' do
subject(:find_or_create) { described_class.find_or_create(project, name, experiment) }
let(:name) { existing_model.name }
let(:project) { existing_model.project }
let(:experiment) { default_experiment }
context 'when model name does not exist in the project' do
let(:name) { 'new_model' }
let(:experiment) { build(:ml_experiments, name: name, project: project) }
it 'creates a model', :aggregate_failures do
expect { find_or_create }.to change { Ml::Model.count }.by(1)
expect(find_or_create.name).to eq(name)
expect(find_or_create.project).to eq(project)
expect(find_or_create.default_experiment).to eq(experiment)
end
end
context 'when model name exists but project is different' do
let(:project) { create(:project) }
let(:experiment) { build(:ml_experiments, name: name, project: project) }
it 'creates a model', :aggregate_failures do
expect { find_or_create }.to change { Ml::Model.count }.by(1)
expect(find_or_create.name).to eq(name)
expect(find_or_create.project).to eq(project)
expect(find_or_create.default_experiment).to eq(experiment)
end
end
context 'when model exists' do
it 'fetches existing model', :aggregate_failures do
expect { find_or_create }.not_to change { Ml::Model.count }
expect(find_or_create).to eq(existing_model)
end
end
end
end

View File

@ -6,6 +6,7 @@ RSpec.describe Ml::ModelVersion, feature_category: :mlops do
using RSpec::Parameterized::TableSyntax
let_it_be(:base_project) { create(:project) }
let_it_be(:model) { create(:ml_models, project: base_project) }
describe 'associations' do
it { is_expected.to belong_to(:project) }
@ -15,7 +16,6 @@ RSpec.describe Ml::ModelVersion, feature_category: :mlops do
describe 'validation' do
let_it_be(:valid_version) { 'valid_version' }
let_it_be(:model) { create(:ml_models, project: base_project) }
let_it_be(:valid_package) do
build_stubbed(:ml_model_package, project: base_project, version: valid_version, name: model.name)
end
@ -87,4 +87,34 @@ RSpec.describe Ml::ModelVersion, feature_category: :mlops do
end
end
end
describe '.find_or_create' do
let_it_be(:existing_model_version) { create(:ml_model_versions, model: model, version: 'abc') }
let(:version) { existing_model_version.version }
let(:package) { nil }
subject(:find_or_create) { described_class.find_or_create(model, version, package) }
context 'if model version exists' do
it 'returns the model version', :aggregate_failures do
expect { find_or_create }.not_to change { Ml::ModelVersion.count }
is_expected.to eq(existing_model_version)
end
end
context 'if model version does not exist' do
let(:version) { 'new_version' }
let(:package) { create(:ml_model_package, project: model.project, name: model.name, version: version) }
it 'creates another model version', :aggregate_failures do
expect { find_or_create }.to change { Ml::ModelVersion.count }.by(1)
model_version = find_or_create
expect(model_version.version).to eq(version)
expect(model_version.model).to eq(model)
expect(model_version.package).to eq(package)
end
end
end
end

View File

@ -100,6 +100,7 @@ RSpec.describe API::Admin::BatchedBackgroundMigrations, feature_category: :datab
expect(json_response.first['id']).to eq(migration.id)
expect(json_response.first['job_class_name']).to eq(migration.job_class_name)
expect(json_response.first['table_name']).to eq(migration.table_name)
expect(json_response.first['column_name']).to eq(migration.column_name)
expect(json_response.first['status']).to eq(migration.status_name.to_s)
expect(json_response.first['progress']).to be_zero
end
@ -151,6 +152,7 @@ RSpec.describe API::Admin::BatchedBackgroundMigrations, feature_category: :datab
expect(json_response.first['id']).to eq(ci_database_migration.id)
expect(json_response.first['job_class_name']).to eq(ci_database_migration.job_class_name)
expect(json_response.first['table_name']).to eq(ci_database_migration.table_name)
expect(json_response.first['column_name']).to eq(ci_database_migration.column_name)
expect(json_response.first['status']).to eq(ci_database_migration.status_name.to_s)
expect(json_response.first['progress']).to be_zero
end

View File

@ -0,0 +1,33 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe ::Ml::FindOrCreateExperimentService, feature_category: :mlops do
let_it_be(:project) { create(:project) }
let_it_be(:user) { project.first_owner }
let_it_be(:existing_experiment) { create(:ml_experiments, project: project, user: user) }
let(:name) { 'new_experiment' }
subject(:new_experiment) { described_class.new(project, name, user).execute }
describe '#execute' do
it 'creates an experiment using Ml::Experiment.find_or_create', :aggregate_failures do
expect(Ml::Experiment).to receive(:find_or_create).and_call_original
expect(new_experiment.name).to eq('new_experiment')
expect(new_experiment.project).to eq(project)
expect(new_experiment.user).to eq(user)
end
context 'when experiment already exists' do
let(:name) { existing_experiment.name }
it 'fetches existing experiment', :aggregate_failures do
expect { new_experiment }.not_to change { Ml::Experiment.count }
expect(new_experiment).to eq(existing_experiment)
end
end
end
end

View File

@ -0,0 +1,45 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe ::Ml::FindOrCreateModelService, feature_category: :mlops do
let_it_be(:existing_model) { create(:ml_models) }
let_it_be(:another_project) { create(:project) }
subject(:create_model) { described_class.new(project, name).execute }
describe '#execute' do
context 'when model name does not exist in the project' do
let(:name) { 'new_model' }
let(:project) { existing_model.project }
it 'creates a model', :aggregate_failures do
expect { create_model }.to change { Ml::Model.count }.by(1)
expect(create_model.name).to eq(name)
end
end
context 'when model name exists but project is different' do
let(:name) { existing_model.name }
let(:project) { another_project }
it 'creates a model', :aggregate_failures do
expect { create_model }.to change { Ml::Model.count }.by(1)
expect(create_model.name).to eq(name)
end
end
context 'when model with name exists' do
let(:name) { existing_model.name }
let(:project) { existing_model.project }
it 'fetches existing model', :aggregate_failures do
expect { create_model }.to change { Ml::Model.count }.by(0)
expect(create_model).to eq(existing_model)
end
end
end
end

View File

@ -0,0 +1,49 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe ::Ml::FindOrCreateModelVersionService, feature_category: :mlops do
let_it_be(:existing_version) { create(:ml_model_versions) }
let_it_be(:another_project) { create(:project) }
let(:package) { nil }
let(:params) do
{
model_name: name,
version: version,
package: package
}
end
subject(:model_version) { described_class.new(project, params).execute }
describe '#execute' do
context 'when model version exists' do
let(:name) { existing_version.name }
let(:version) { existing_version.version }
let(:project) { existing_version.project }
it 'returns existing model version', :aggregate_failures do
expect { model_version }.to change { Ml::ModelVersion.count }.by(0)
expect(model_version).to eq(existing_version)
end
end
context 'when model version does not exist' do
let(:project) { existing_version.project }
let(:name) { 'a_new_model' }
let(:version) { 'a_new_version' }
let(:package) { create(:ml_model_package, project: project, name: name, version: version) }
it 'creates a new model version', :aggregate_failures do
expect { model_version }.to change { Ml::ModelVersion.count }
expect(model_version.name).to eq(name)
expect(model_version.version).to eq(version)
expect(model_version.package).to eq(package)
end
end
end
end