Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
45c024c949
commit
50599a2477
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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')"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1 @@
|
|||
b8024331f3225efecfb7b8a02c5dbdccf27fe80f14d2241e6753b87837895a9c
|
||||
|
|
@ -0,0 +1 @@
|
|||
813dd1b39691e4b9b1ab5e5e442ec60036fb750fe2c8c086bc6134280f3c2349
|
||||
|
|
@ -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:
|
||||
|
||||
|
|
|
|||
|
|
@ -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. |
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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`.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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/
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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 });
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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' }
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue