Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2025-02-14 15:09:52 +00:00
parent 45c024c949
commit 50599a2477
30 changed files with 341 additions and 85 deletions

View File

@ -62,13 +62,7 @@ export default {
<slot name="filepath-prepend"></slot>
<template v-if="fileName">
<file-icon
:file-name="fileName"
:language="blob.language"
:size="16"
aria-hidden="true"
css-classes="gl-mr-3"
/>
<file-icon :file-name="fileName" :size="16" aria-hidden="true" css-classes="gl-mr-3" />
<component
:is="showAsLink ? 'gl-link' : 'strong'"
v-bind="linkHref"

View File

@ -25,11 +25,6 @@ export default {
type: String,
required: true,
},
language: {
type: String,
required: false,
default: '',
},
fileMode: {
type: String,
required: false,
@ -71,16 +66,10 @@ export default {
return this.fileMode === FILE_SYMLINK_MODE;
},
spriteHref() {
const iconName = this.submodule ? 'folder-git' : this.iconForFile;
const iconName = this.submodule ? 'folder-git' : getIconForFile(this.fileName) || 'file';
return `${gon.sprite_file_icons}#${iconName}`;
},
iconForFile() {
const languageIcon = this.language && getIconForFile(this.language);
const fileNameIcon = getIconForFile(this.fileName);
return languageIcon || fileNameIcon || 'file';
},
folderIconName() {
return this.opened ? 'folder-open' : 'folder';
},

View File

@ -12,6 +12,7 @@ import {
RELATED_ITEM_ID_URL_QUERY_PARAM,
WORK_ITEM_TYPE_NAME_LOWERCASE_MAP,
WORK_ITEM_TYPE_ENUM_INCIDENT,
WORK_ITEM_TYPE_VALUE_MAP,
} from '../constants';
import CreateWorkItem from './create_work_item.vue';
import CreateWorkItemCancelConfirmationModal from './create_work_item_cancel_confirmation_modal.vue';
@ -107,12 +108,28 @@ export default {
this.$router.options.routes.some((route) => route.name === 'workItem')
);
},
newWorkItemPathQuery() {
let query = '';
let previousQueryParam = false;
// Only add query string if there's a work item type selected
if (this.selectedWorkItemTypeName && this.useVueRouter) {
query += previousQueryParam ? '&' : '?';
// eslint-disable-next-line @gitlab/require-i18n-strings
query += `type=${this.selectedWorkItemTypeName}`;
previousQueryParam = true;
}
if (this.relatedItem) {
query += previousQueryParam ? '&' : '?';
query += `${RELATED_ITEM_ID_URL_QUERY_PARAM}=${this.relatedItem.id}`;
}
return query;
},
newWorkItemPath() {
return newWorkItemPath({
fullPath: this.fullPath,
isGroup: this.isGroup,
workItemTypeName: this.workItemTypeName,
query: this.relatedItem ? `?${RELATED_ITEM_ID_URL_QUERY_PARAM}=${this.relatedItem.id}` : '',
query: this.newWorkItemPathQuery,
});
},
selectedWorkItemTypeLowercase() {
@ -204,7 +221,8 @@ export default {
// Take incidents to the legacy detail view with a full page load
if (
this.useVueRouter &&
this.selectedWorkItemTypeName !== WORK_ITEM_TYPE_ENUM_INCIDENT
WORK_ITEM_TYPE_VALUE_MAP[workItem?.workItemType?.name] !==
WORK_ITEM_TYPE_ENUM_INCIDENT
) {
this.$router.push({ name: 'workItem', params: { iid: workItem.iid } });
} else {
@ -217,11 +235,6 @@ export default {
this.hideCreateModal();
},
redirectToNewPage(event) {
if (isMetaClick(event)) {
// opening in a new tab
return;
}
event.preventDefault();
if (this.useVueRouter) {

View File

@ -2,7 +2,14 @@
import { visitUrl, getParameterByName, updateHistory, removeParams } from '~/lib/utils/url_utility';
import CreateWorkItem from '../components/create_work_item.vue';
import CreateWorkItemCancelConfirmationModal from '../components/create_work_item_cancel_confirmation_modal.vue';
import { ROUTES, RELATED_ITEM_ID_URL_QUERY_PARAM } from '../constants';
import {
ROUTES,
RELATED_ITEM_ID_URL_QUERY_PARAM,
BASE_ALLOWED_CREATE_TYPES,
WORK_ITEM_TYPE_ENUM_ISSUE,
WORK_ITEM_TYPE_VALUE_MAP,
WORK_ITEM_TYPE_ENUM_INCIDENT,
} from '../constants';
import workItemRelatedItemQuery from '../graphql/work_item_related_item.query.graphql';
export default {
@ -53,12 +60,30 @@ export default {
},
},
},
computed: {
isIssue() {
return this.workItemTypeName === WORK_ITEM_TYPE_ENUM_ISSUE;
},
isIncident() {
return this.workItemTypeName === WORK_ITEM_TYPE_ENUM_INCIDENT;
},
allowedWorkItemTypes() {
if (this.isIssue || this.isIncident) {
return BASE_ALLOWED_CREATE_TYPES;
}
return [];
},
},
methods: {
updateWorkItemType(type) {
this.workItemType = type;
},
workItemCreated({ workItem, numberOfDiscussionsResolved }) {
if (this.$router) {
if (
this.$router &&
WORK_ITEM_TYPE_VALUE_MAP[this.workItemType] !== WORK_ITEM_TYPE_ENUM_INCIDENT
) {
const routerPushObject = {
name: ROUTES.workItem,
params: { iid: workItem.iid },
@ -120,6 +145,8 @@ export default {
:is-group="isGroup"
:related-item="relatedItem"
:should-discard-draft="shouldDiscardDraft"
:always-show-work-item-type-select="isIncident || isIssue"
:allowed-work-item-types="allowedWorkItemTypes"
@updateType="updateWorkItemType($event)"
@confirmCancel="handleConfirmCancellation"
@discardDraft="handleDiscardDraft('createPage')"

View File

@ -74,10 +74,6 @@ class WikiPage
if conflict.present?
transaction(requires_new: false) do
conflict.events.each_batch do |batch|
batch.update_all(target_id: meta.id)
end
conflict.todos.each_batch do |batch|
batch.update_all(target_id: meta.id)
end

View File

@ -0,0 +1,26 @@
---
key_path: counts.count_total_ci_runners_created_by_authenticated_user
description: Count of runners created from the UI or through the API
product_group: runner
product_categories:
- fleet_visibility
- runner
performance_indicator_type: []
value_type: number
status: active
milestone: '17.9'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/181443
time_frame:
- 28d
- 7d
- all
data_source: internal_events
data_category: optional
tiers:
- free
- premium
- ultimate
events:
- name: create_ci_runner
filter:
property: authenticated_user

View File

@ -0,0 +1,24 @@
---
key_path: counts.count_total_ci_runners_created
description: Count of created runners
product_group: runner
product_categories:
- fleet_visibility
- runner
performance_indicator_type: []
value_type: number
status: active
milestone: '17.9'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/181443
time_frame:
- 28d
- 7d
- all
data_source: internal_events
data_category: optional
tiers:
- free
- premium
- ultimate
events:
- name: create_ci_runner

View File

@ -5,4 +5,4 @@ feature_category: wiki
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/154560
milestone: '17.1'
queued_migration_version: 20240529112047
finalized_by: # version of the migration that finalized this BBM
finalized_by: '20250213231429'

View File

@ -5,4 +5,4 @@ feature_category: global_search
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/157849
milestone: '17.2'
queued_migration_version: 20240629011500
finalized_by: # version of the migration that finalized this BBM
finalized_by: '20250212025529'

View File

@ -0,0 +1,21 @@
# frozen_string_literal: true
class FinalizeHkBackfillZoektReplicas < Gitlab::Database::Migration[2.2]
milestone '17.9'
disable_ddl_transaction!
restrict_gitlab_migration gitlab_schema: :gitlab_main
def up
ensure_batched_background_migration_is_finished(
job_class_name: 'BackfillZoektReplicas',
table_name: :zoekt_indices,
column_name: :id,
job_arguments: [],
finalize: true
)
end
def down; end
end

View File

@ -0,0 +1,21 @@
# frozen_string_literal: true
class FinalizeHkBackfillWikiPageSlugsProjectId < Gitlab::Database::Migration[2.2]
milestone '17.9'
disable_ddl_transaction!
restrict_gitlab_migration gitlab_schema: :gitlab_main_cell
def up
ensure_batched_background_migration_is_finished(
job_class_name: 'BackfillWikiPageSlugsProjectId',
table_name: :wiki_page_slugs,
column_name: :id,
job_arguments: [:project_id, :wiki_page_meta, :project_id, :wiki_page_meta_id],
finalize: true
)
end
def down; end
end

View File

@ -0,0 +1 @@
b8024331f3225efecfb7b8a02c5dbdccf27fe80f14d2241e6753b87837895a9c

View File

@ -0,0 +1 @@
813dd1b39691e4b9b1ab5e5e442ec60036fb750fe2c8c086bc6134280f3c2349

View File

@ -20,7 +20,7 @@ When working with GitLab Duo Self-Hosted, you might encounter issues.
Before you begin troubleshooting, you should:
- Be able to access open the [`gitlab-rails` console](../operations/rails_console.md).
- Be able to access the [`gitlab-rails` console](../operations/rails_console.md).
- Open a shell in the AI gateway Docker image.
- Know the endpoint where your:
- AI gateway is hosted.
@ -35,8 +35,7 @@ Before you begin troubleshooting, you should:
## Use debugging scripts
We provide two debugging scripts to help administrators verify their self-hosted
model configuration.
We provide two debugging scripts to help administrators verify their self-hosted model configuration.
1. Debug the GitLab to AI gateway connection. From your GitLab instance, run the
[Rake task](../../raketasks/_index.md):
@ -155,7 +154,7 @@ This missing configuration might be because of either of the following:
- The license is not valid. To resolve, [check or update your license](../license_file.md#see-current-license-information).
- GitLab Duo was not configured to use a self-hosted model. To resolve, [check if the GitLab instance is configured to use self-hosted models](#check-if-gitlab-instance-is-configured-to-use-self-hosted-models).
## Check if GitLab instance is configured to use self-hosted-models
## Check if GitLab instance is configured to use self-hosted models
To check if GitLab Duo was configured correctly:

View File

@ -22442,11 +22442,9 @@ Represents a ComplianceRequirement associated with a ComplianceFramework.
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="compliancerequirementcompliancerequirementscontrols"></a>`complianceRequirementsControls` | [`ComplianceRequirementsControlConnection`](#compliancerequirementscontrolconnection) | Compliance controls of the compliance requirement. (see [Connections](#connections)) |
| <a id="compliancerequirementcontrolexpression"></a>`controlExpression` | [`String`](#string) | Control expression of the compliance requirement. |
| <a id="compliancerequirementdescription"></a>`description` | [`String!`](#string) | Description of the compliance requirement. |
| <a id="compliancerequirementid"></a>`id` | [`ID!`](#id) | Compliance requirement ID. |
| <a id="compliancerequirementname"></a>`name` | [`String!`](#string) | Name of the compliance requirement. |
| <a id="compliancerequirementrequirementtype"></a>`requirementType` | [`String!`](#string) | Type of the compliance requirement. |
### `ComplianceRequirementControl`
@ -46080,7 +46078,6 @@ Attributes for defining a CI/CD variable.
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="compliancerequirementinputcontrolexpression"></a>`controlExpression` | [`String`](#string) | Control expression for the compliance requirement. |
| <a id="compliancerequirementinputdescription"></a>`description` | [`String`](#string) | New description for the compliance requirement. |
| <a id="compliancerequirementinputname"></a>`name` | [`String`](#string) | New name for the compliance requirement. |

View File

@ -1686,6 +1686,7 @@ POST /groups/:id/share
| `group_id` | integer | yes | The ID of the group to share with |
| `group_access` | integer | yes | The [role (`access_level`)](members.md#roles) to grant the group |
| `expires_at` | string | no | Share expiration date in ISO 8601 format: 2016-09-26 |
| `member_role_id` | integer | no | The ID of a [Custom Role](../user/custom_roles.md#assign-a-custom-role-to-an-invited-group) to assign to the invited group |
#### Delete the link that shares a group with another group

View File

@ -739,7 +739,11 @@ For example:
> - Support for work items (tasks, objectives, and key results) [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/390854) in GitLab 16.0.
To include the title in the rendered link of an issue, task, objective, key result, merge request, or epic:
<!-- When epics as work items are generally available and `work_item_epics` flag is removed,
refactor the link below and add a history note -->
To include the title in the rendered link of an epic ([using the new look](group/epics/epic_work_items.md)),
issue, task, objective, key result, merge request, or epic:
- Add a plus (`+`) at the end of the reference.
@ -752,11 +756,15 @@ URL references like `https://gitlab.com/gitlab-org/gitlab/-/issues/1234+` are al
> - Support for issues and merge requests [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/386937) in GitLab 15.10.
> - Support for work items (tasks, objectives, and key results) [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/390854) in GitLab 16.0.
To include an extended summary in the rendered link of an issue, task, objective, key result, or merge request:
<!-- When epics as work items are generally available and `work_item_epics` flag is removed,
refactor the link below and add a history note -->
To include an extended summary in the rendered link of an epic ([using the new look](group/epics/epic_work_items.md)),
issue, task, objective, key result, or merge request:
- Add a `+s` at the end of the reference.
Summary includes information about **assignees**, **milestone** and **health status** of referenced item.
Summary includes information about **assignees**, **milestone** and **health status**, as applicable by work item type, of referenced item.
For example, a reference like `#123+s` is rendered as
`The issue title (#123) • First Assignee, Second Assignee+ • v15.10 • Needs attention`.

View File

@ -130,7 +130,26 @@ with mlflow.start_run():
model.fit(X_train, y_train)
# Log the model using MLflow sklearn mode flavour
mlflow.sklearn.log_model(model, artifact_path="model")
mlflow.sklearn.log_model(model, artifact_path="")
```
### Loading a run
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/509595) in GitLab 17.9.
You can load a run from the GitLab model registry to, for example, make predictions.
```python
import mlflow
import mlflow.pyfunc
run_id = "<your_run_id>"
download_path = "models" # Local folder to download to
mlflow.pyfunc.load_model(f"runs:/{run_id}/", dst_path=download_path)
sample_input = [[1,0,3,4],[2,0,1,2]]
model.predict(data=sample_input)
```
### Associating a run to a CI/CD job

View File

@ -46,7 +46,7 @@ The next time someone tries to access your website and the access control is
enabled, they're presented with a page to sign in to GitLab and verify they
can access the website.
## Restrict Pages access to project members
## Restrict Pages access to project members for the group and its subgroups
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/254962) in GitLab 17.9.

View File

@ -21,21 +21,22 @@ module API
private
CANDIDATE_PREFIX = 'candidate:'
MLFLOW_ARTIFACTS_PREFIX = 'mlflow-artifacts'
def run_id
object.eid.to_s
end
def artifact_uri
uri = if object.package&.generic?
generic_package_uri
elsif object.model_version_id
model_version_uri
else
ml_model_candidate_uri
end
expose_url(uri)
if object.package&.generic?
expose_url(generic_package_uri)
elsif object.model_version_id
expose_url(model_version_uri)
elsif object.package&.version&.start_with?('candidate_')
"#{MLFLOW_ARTIFACTS_PREFIX}:/#{CANDIDATE_PREFIX}#{object.iid}"
else
expose_url(ml_model_candidate_uri)
end
end
# Example: http://127.0.0.1:3000/api/v4/projects/20/packages/ml_models/1/files/

View File

@ -156,6 +156,11 @@ module API
::Packages::PackageFileFinder.new(package, file_path).execute || resource_not_found!
end
def find_run_artifact(project, version, file_path)
package = ::Ml::Candidate.with_project_id_and_iid(project, version).package
::Packages::PackageFileFinder.new(package, file_path).execute || resource_not_found!
end
def list_model_artifacts(project, version)
model_version = ::Ml::ModelVersion.by_project_id_and_id(project, version)
resource_not_found! unless model_version && model_version.package
@ -163,9 +168,23 @@ module API
model_version.package.installable_package_files
end
def list_run_artifacts(project, version)
run = ::Ml::Candidate.with_project_id_and_iid(project, version)
resource_not_found! unless run&.package
run.package.installable_package_files
end
def model
@model ||= find_model(user_project, params[:name])
end
def candidate_version?(model_version)
return false unless model_version
model_version&.start_with?('candidate:')
end
end
end
end

View File

@ -6,6 +6,8 @@ module API
# MLFlow integration API, replicating the Rest API https://www.mlflow.org/docs/latest/rest-api.html#rest-api
module Ml
module MlflowArtifacts
CANDIDATE_PREFIX = 'candidate:'
class Artifacts < ::API::Base
feature_category :mlops
helpers ::API::Helpers::PackagesHelpers
@ -30,15 +32,25 @@ module API
# MLflow handles directories differently than GitLab does so when MLflow checks if a path is a directory
# we return an empty array as 404s would cause issues for MLflow
files = path.present? ? [] : list_model_artifacts(user_project, model_version).all
files = if candidate_version?(model_version)
run_version = model_version.delete_prefix(CANDIDATE_PREFIX)
path.present? ? [] : list_run_artifacts(user_project, run_version).all
else
path.present? ? [] : list_model_artifacts(user_project, model_version).all
end
package_files = { files: files }
present package_files, with: Entities::Ml::MlflowArtifacts::ArtifactsList
end
get 'artifacts/:model_version/*file_path', format: false, urgency: :low do
present_package_file!(find_model_artifact(user_project, params[:model_version],
CGI.escape(params[:file_path])))
if candidate_version?(params[:model_version])
version = params[:model_version].delete_prefix(CANDIDATE_PREFIX)
present_package_file!(find_run_artifact(user_project, version, CGI.escape(params[:file_path])))
else
present_package_file!(find_model_artifact(user_project, params[:model_version],
CGI.escape(params[:file_path])))
end
end
end
end

View File

@ -19,7 +19,6 @@ exports[`Repository table row component renders a symlink table row 1`] = `
cssclasses="gl-relative file-icon"
filemode="120000"
filename="test"
language=""
size="16"
/>
<gl-truncate-stub
@ -78,7 +77,6 @@ exports[`Repository table row component renders table row 1`] = `
cssclasses="gl-relative file-icon"
filemode=""
filename="test"
language=""
size="16"
/>
<gl-truncate-stub
@ -137,7 +135,6 @@ exports[`Repository table row component renders table row for path with special
cssclasses="gl-relative file-icon"
filemode=""
filename="test"
language=""
size="16"
/>
<gl-truncate-stub

View File

@ -38,23 +38,6 @@ describe('File Icon component', () => {
expect(getIconName()).toBe(iconName);
});
it.each`
fileName | language | iconName
${'test-objective-c.m'} | ${''} | ${'c'}
${'test-matlab.m'} | ${'matlab'} | ${'matlab'}
`('should render a $iconName icon based on file language', ({ fileName, language, iconName }) => {
createComponent({ fileName, language });
expect(getIconName()).toBe(iconName);
});
it('should render a file name icon, if a language is not specified', () => {
createComponent({
fileName: 'package.js',
language: 'something-random',
});
expect(getIconName()).toBe('javascript');
});
it('should render a standard folder icon', () => {
createComponent({
fileName: 'js',

View File

@ -1,4 +1,5 @@
import { nextTick } from 'vue';
import Vue, { nextTick } from 'vue';
import VueRouter from 'vue-router';
import { GlDisclosureDropdownItem, GlModal } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import waitForPromises from 'helpers/wait_for_promises';
@ -14,7 +15,19 @@ import CreateWorkItemCancelConfirmationModal from '~/work_items/components/creat
const showToast = jest.fn();
describe('CreateWorkItemModal', () => {
Vue.use(VueRouter);
let wrapper;
const router = new VueRouter({
routes: [
{
path: `/`,
name: 'home',
component: CreateWorkItemModal,
},
],
mode: 'history',
base: 'basePath',
});
const findTrigger = () => wrapper.find('[data-testid="new-epic-button"]');
const findDropdownItem = () => wrapper.findComponent(GlDisclosureDropdownItem);
@ -50,6 +63,7 @@ describe('CreateWorkItemModal', () => {
stubs: {
GlModal,
},
router,
});
};
@ -57,7 +71,7 @@ describe('CreateWorkItemModal', () => {
createComponent();
await waitForPromises();
findForm().vm.$emit('workItemCreated', { webUrl: '/' });
findForm().vm.$emit('workItemCreated', { webUrl: '/', workItemType: { name: 'Epic' } });
expect(showToast).toHaveBeenCalledWith('Epic created', expect.any(Object));
});

View File

@ -90,6 +90,17 @@ describe('Create work item page component', () => {
expect(visitUrl).toHaveBeenCalledWith('/work_items/1234');
});
it('reloads the page after create if work item created is an incident', () => {
createComponent();
findCreateWorkItem().vm.$emit('workItemCreated', {
workItem: { webUrl: '/work_items/1234', workItemType: { name: 'Incident' } },
numberOfDiscussionsResolved: '',
});
expect(visitUrl).toHaveBeenCalledWith('/work_items/1234');
});
it('calls router.push after create if router is present', () => {
const pushMock = jest.fn();
createComponent({ push: pushMock });

View File

@ -88,6 +88,14 @@ RSpec.describe API::Entities::Ml::Mlflow::RunInfo, feature_category: :mlops do
expect(subject[:artifact_uri]).to eq("http://localhost/api/v4/projects/#{candidate.project_id}/packages/generic#{candidate.artifact_root}")
end
end
context 'when candidate has no file or generic package' do
let!(:candidate) { create(:ml_candidates, :with_ml_model, name: 'candidate_1') }
it 'returns a string with no package' do
expect(subject[:artifact_uri]).to eq("mlflow-artifacts:/candidate:#{candidate.iid}")
end
end
end
describe 'lifecycle_stage' do

View File

@ -123,4 +123,52 @@ RSpec.describe API::Ml::Mlflow::ApiHelpers, feature_category: :mlops do
end
end
end
describe '#icandidate_version?' do
describe 'when version is nil' do
let(:version) { nil }
it 'returns false' do
expect(candidate_version?(version)).to be false
end
end
describe 'when version has candidate prefix' do
let(:version) { 'candidate:1' }
it 'returns true' do
expect(candidate_version?(version)).to be true
end
end
describe 'when version does not have candidate prefix' do
let(:version) { '1' }
it 'returns false' do
expect(candidate_version?(version)).to be false
end
end
end
describe '#find_run_artifact' do
let_it_be(:project) { create(:project) }
let_it_be(:candidate) { create(:ml_candidates, :with_ml_model, project: project) }
let_it_be(:candidate_package_file) { create(:package_file, :ml_model, package: candidate.package) }
it 'returns list of files' do
expect(find_run_artifact(project, candidate.iid, candidate_package_file.file_name)).to eq candidate_package_file
end
end
describe '#list_run_artifacts' do
let_it_be(:project) { create(:project) }
let_it_be(:candidate) { create(:ml_candidates, :with_ml_model, project: project) }
let_it_be(:candidate_package_file) { create(:package_file, :ml_model, package: candidate.package) }
let_it_be(:candidate_package_file_2) { create(:package_file, :ml_model, package: candidate.package) }
it 'returns list of files' do
expect(list_run_artifacts(project,
candidate.iid)).to match_array [candidate_package_file, candidate_package_file_2]
end
end
end

View File

@ -13,6 +13,8 @@ RSpec.describe API::Ml::MlflowArtifacts::Artifacts, feature_category: :mlops do
let_it_be(:model_version) { create(:ml_model_versions, :with_package, model: model, version: version) }
let_it_be(:package_file) { create(:package_file, :ml_model, package: model_version.package) }
let_it_be(:model_version_no_package) { create(:ml_model_versions, model: model, version: '0.0.2') }
let_it_be(:candidate) { create(:ml_candidates, :with_ml_model, project: project) }
let_it_be(:candidate_package_file) { create(:package_file, :ml_model, package: candidate.package) }
let_it_be(:tokens) do
{
@ -89,6 +91,19 @@ RSpec.describe API::Ml::MlflowArtifacts::Artifacts, feature_category: :mlops do
end
end
context 'when the model version is a candidate version' do
let(:route) do
"/projects/#{project_id}/ml/mlflow/api/2.0/mlflow-artifacts/artifacts?path=candidate:#{candidate.iid}/MlModel"
end
it 'returns an empty list of artifacts', :aggregate_failures do
is_expected.to have_gitlab_http_status(:ok)
expect(json_response).to have_key('files')
expect(json_response['files']).to be_an_instance_of(Array)
expect(json_response['files']).to be_empty
end
end
it_behaves_like 'MLflow|an authenticated resource'
it_behaves_like 'MLflow|a read-only model registry resource'
end
@ -109,6 +124,22 @@ RSpec.describe API::Ml::MlflowArtifacts::Artifacts, feature_category: :mlops do
end
end
context 'when the model version is a candidate' do
let_it_be(:file) { candidate_package_file.file_name }
let(:route) do
"/projects/#{project_id}/ml/mlflow/api/2.0/mlflow-artifacts/artifacts/candidate:#{candidate.iid}/#{file}"
end
it 'returns the artifact file', :aggregate_failures do
is_expected.to have_gitlab_http_status(:ok)
expect(response.headers['Content-Disposition']).to match(
"attachment; filename=\"#{candidate_package_file.file_name}\""
)
expect(response.body).to eq(candidate_package_file.file.read)
expect(response.headers['Content-Length']).to eq(candidate_package_file.size.to_s)
end
end
context 'when the file does not exist' do
let(:file_path) { 'non_existent_file.txt' }

View File

@ -51,7 +51,6 @@ RSpec.shared_examples 'creating wiki page meta record examples' do
let!(:newer_meta) { create(:wiki_page_meta, container: container, canonical_slug: 'foobar') }
let!(:todo) { create(:todo, target: newer_meta) }
let!(:event) { create(:event, target: newer_meta) }
before do
slug = newer_meta.slugs.first
@ -70,10 +69,6 @@ RSpec.shared_examples 'creating wiki page meta record examples' do
it 'moves associated todos to the older record' do
expect { find_record }.to change { todo.reload.target }.from(newer_meta).to(older_meta)
end
it 'moves associated events to the older record' do
expect { find_record }.to change { event.reload.target }.from(newer_meta).to(older_meta)
end
end
context 'when the wiki page is not valid' do