Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2023-10-16 21:12:54 +00:00
parent 5fcd4e5fbc
commit f5ea25c365
82 changed files with 746 additions and 334 deletions

View File

@ -0,0 +1,3 @@
import ShowMlModel from './show_ml_model.vue';
export { ShowMlModel };

View File

@ -0,0 +1,16 @@
<script>
export default {
name: 'ShowMlModelApp',
components: {},
props: {
model: {
type: Object,
required: true,
},
},
};
</script>
<template>
<div>{{ model.name }}</div>
</template>

View File

@ -0,0 +1,4 @@
import { initSimpleApp } from '~/helpers/init_simple_app_helper';
import { ShowMlModel } from '~/ml/model_registry/apps';
initSimpleApp('#js-mount-show-ml-model', ShowMlModel);

View File

@ -278,20 +278,17 @@ export default {
@saveReorder="$emit('saveReorder', $event)"
/>
</template>
<div v-if="!shouldShowTokenBody && !isFormVisible">
<p class="gl-new-card-empty">
{{ emptyStateMessage }}
<gl-link
v-if="hasHelpPath"
:href="helpPath"
target="_blank"
data-testid="help-link"
:aria-label="helpLinkText"
>
{{ __('Learn more.') }}
</gl-link>
</p>
</div>
<p v-if="!shouldShowTokenBody && !isFormVisible" class="gl-new-card-empty">
{{ emptyStateMessage }}
<gl-link
v-if="hasHelpPath"
:href="helpPath"
data-testid="help-link"
:aria-label="helpLinkText"
>
{{ __('Learn more.') }}
</gl-link>
</p>
</div>
</gl-card>
</div>

View File

@ -59,19 +59,17 @@ const updateItemAccess = (
const neverAccessed = !lastAccessedOn;
const shouldUpdate = neverAccessed || Math.abs(now - lastAccessedOn) / FIFTEEN_MINUTES_IN_MS > 1;
if (shouldUpdate && gon.features?.serverSideFrecentNamespaces) {
try {
axios({
url: trackVisitsPath,
method: 'POST',
data: {
type: namespace,
id: contextItem.id,
},
});
} catch (e) {
if (shouldUpdate) {
axios({
url: trackVisitsPath,
method: 'POST',
data: {
type: namespace,
id: contextItem.id,
},
}).catch((e) => {
Sentry.captureException(e);
}
});
}
return {

View File

@ -43,13 +43,9 @@ export default {
type: Boolean,
required: true,
},
childPath: {
type: String,
required: true,
},
/*
/*
This flag is added to manage between two different work items; Task and Objective/Key result.
Status icon is shown on the task while the actual task icon is shown on any Objective/Key result.
Status icon is shown on the task while the actual task icon is shown on any Objective/Key result.
*/
showTaskIcon: {
type: Boolean,
@ -157,9 +153,8 @@ export default {
/>
</span>
<gl-link
:href="childPath"
:href="childItem.webUrl"
class="gl-overflow-break-word gl-font-weight-semibold"
data-testid="item-title"
@click="$emit('click', $event)"
@mouseover="$emit('mouseover')"
@mouseout="$emit('mouseout')"

View File

@ -13,7 +13,6 @@ import {
WIDGET_TYPE_HIERARCHY,
WORK_ITEM_NAME_TO_ICON_MAP,
} from '../../constants';
import { workItemPath } from '../../utils';
import getWorkItemTreeQuery from '../../graphql/work_item_tree.query.graphql';
import WorkItemLinkChildContents from '../shared/work_item_link_child_contents.vue';
import WorkItemTreeChildren from './work_item_tree_children.vue';
@ -27,7 +26,6 @@ export default {
directives: {
GlTooltip: GlTooltipDirective,
},
inject: ['fullPath'],
props: {
canUpdate: {
type: Boolean,
@ -90,9 +88,6 @@ export default {
stateTimestampTypeText() {
return this.isItemOpen ? __('Created') : __('Closed');
},
childPath() {
return workItemPath(this.fullPath, this.childItem.iid);
},
chevronType() {
return this.isExpanded ? 'chevron-down' : 'chevron-right';
},
@ -236,7 +231,6 @@ export default {
:can-update="canUpdate"
:parent-work-item-id="issuableGid"
:work-item-type="workItemType"
:child-path="childPath"
@click="$emit('click', $event)"
@removeChild="$emit('removeChild', childItem)"
/>

View File

@ -1,6 +1,5 @@
<script>
import WorkItemLinkChildContents from '../shared/work_item_link_child_contents.vue';
import { workItemPath } from '../../utils';
export default {
components: {
@ -20,15 +19,6 @@ export default {
type: Boolean,
required: true,
},
workItemFullPath: {
type: String,
required: true,
},
},
methods: {
linkedItemPath(fullPath, id) {
return workItemPath(fullPath, id);
},
},
};
</script>
@ -51,7 +41,6 @@ export default {
<work-item-link-child-contents
:child-item="linkedItem.workItem"
:can-update="canUpdate"
:child-path="linkedItemPath(workItemFullPath, linkedItem.workItem.iid)"
:show-task-icon="true"
@click="$emit('showModal', { event: $event, child: linkedItem.workItem })"
@removeChild="$emit('removeLinkedItem', linkedItem.workItem)"

View File

@ -263,7 +263,6 @@ export default {
}"
:linked-items="linksBlocks"
:heading="$options.i18n.blockingTitle"
:work-item-full-path="workItemFullPath"
:can-update="canAdminWorkItemLink"
@showModal="$emit('showModal', { event: $event.event, modalWorkItem: $event.child })"
@removeLinkedItem="removeLinkedItem"
@ -276,7 +275,6 @@ export default {
}"
:linked-items="linksIsBlockedBy"
:heading="$options.i18n.blockedByTitle"
:work-item-full-path="workItemFullPath"
:can-update="canAdminWorkItemLink"
@showModal="$emit('showModal', { event: $event.event, modalWorkItem: $event.child })"
@removeLinkedItem="removeLinkedItem"
@ -285,7 +283,6 @@ export default {
v-if="linksRelatesTo.length"
:linked-items="linksRelatesTo"
:heading="$options.i18n.relatedToTitle"
:work-item-full-path="workItemFullPath"
:can-update="canAdminWorkItemLink"
@showModal="$emit('showModal', { event: $event.event, modalWorkItem: $event.child })"
@removeLinkedItem="removeLinkedItem"

View File

@ -120,6 +120,7 @@ fragment WorkItemWidgets on WorkItemWidget {
state
createdAt
closedAt
webUrl
widgets {
...WorkItemMetadataWidgets
}

View File

@ -1,4 +1,3 @@
import { joinPaths } from '~/lib/utils/url_utility';
import {
WIDGET_TYPE_ASSIGNEES,
WIDGET_TYPE_HEALTH_STATUS,
@ -43,7 +42,3 @@ export const markdownPreviewPath = (fullPath, iid) =>
`${
gon.relative_url_root || ''
}/${fullPath}/preview_markdown?target_type=WorkItem&target_id=${iid}`;
export const workItemPath = (fullPath, workItemIid) => {
return joinPaths(gon?.relative_url_root || '/', fullPath, '-', 'work_items', workItemIid);
};

View File

@ -0,0 +1 @@
#js-mount-show-ml-model{ data: { view_model: view_model } }

View File

@ -0,0 +1,27 @@
# frozen_string_literal: true
module Projects
module Ml
class ShowMlModelComponent < ViewComponent::Base
attr_reader :model
def initialize(model:)
@model = model.present
end
private
def view_model
vm = {
model: {
id: model.id,
name: model.name,
path: model.path
}
}
Gitlab::Json.generate(vm)
end
end
end
end

View File

@ -4,6 +4,7 @@ module Projects
module Ml
class ModelsController < ::Projects::ApplicationController
before_action :check_feature_enabled
before_action :set_model, only: [:show]
feature_category :mlops
MAX_MODELS_PER_PAGE = 20
@ -14,11 +15,19 @@ module Projects
.keyset_paginate(cursor: params[:cursor], per_page: MAX_MODELS_PER_PAGE)
end
def show; end
private
def check_feature_enabled
render_404 unless can?(current_user, :read_model_registry, @project)
end
def set_model
@model = ::Ml::Model.by_project_id_and_id(@project, params[:model_id])
render_404 unless @model
end
end
end
end

View File

@ -5,7 +5,6 @@ module Users
feature_category :navigation
def create
return head :not_found unless Feature.enabled?(:server_side_frecent_namespaces, current_user)
return head :bad_request unless params[:type].present? && params[:id].present?
Users::TrackNamespaceVisitsWorker.perform_async(params[:type], params[:id], current_user.id, DateTime.now) # rubocop:disable CodeReuse/Worker

View File

@ -138,24 +138,18 @@ module DiffHelper
def submodule_diff_compare_link(diff_file)
compare_url = submodule_links(diff_file.blob, diff_file.content_sha, diff_file.repository, diff_file)&.compare
return '' unless compare_url
link = ""
link_text = [
_('Compare'),
' ',
content_tag(:span, Commit.truncate_sha(diff_file.old_blob.id), class: 'commit-sha'),
'...',
content_tag(:span, Commit.truncate_sha(diff_file.blob.id), class: 'commit-sha')
].join('').html_safe
if compare_url
link_text = [
_('Compare'),
' ',
content_tag(:span, Commit.truncate_sha(diff_file.old_blob.id), class: 'commit-sha'),
'...',
content_tag(:span, Commit.truncate_sha(diff_file.blob.id), class: 'commit-sha')
].join('').html_safe
tooltip = _('Compare submodule commit revisions')
link = content_tag(:span, link_to(link_text, compare_url, class: 'btn gl-button has-tooltip', title: tooltip), class: 'submodule-compare')
end
link
tooltip = _('Compare submodule commit revisions')
link_button_to link_text, compare_url, class: 'has-tooltip submodule-compare', title: tooltip
end
def diff_file_blob_raw_url(diff_file, only_path: false)
@ -280,9 +274,8 @@ module DiffHelper
end
def toggle_whitespace_link(url, options)
options[:class] = [*options[:class], 'btn gl-button btn-default'].join(' ')
toggle_text = hide_whitespace? ? s_('Diffs|Show whitespace changes') : s_('Diffs|Hide whitespace changes')
link_to toggle_text, url, class: options[:class]
link_button_to toggle_text, url, class: options[:class]
end
def code_navigation_path(diffs)

View File

@ -32,5 +32,9 @@ module Ml
create_with(default_experiment: experiment)
.find_or_create_by(project: project, name: name)
end
def self.by_project_id_and_id(project_id, id)
find_by(project_id: project_id, id: id)
end
end
end

View File

@ -1265,6 +1265,12 @@ class Repository
Gitlab::Git::ObjectPool.init_from_gitaly(gitaly_object_pool, source_project&.repository)
end
def get_file_attributes(revision, paths, attributes)
raw_repository
.get_file_attributes(revision, paths, attributes)
.map(&:to_h)
end
private
def ancestor_cache_key(ancestor_id, descendant_id)

View File

@ -13,5 +13,9 @@ module Ml
Gitlab::Routing.url_helpers.project_package_path(model.project, model.latest_version.package_id)
end
def path
Gitlab::Routing.url_helpers.project_ml_model_path(model.project, model.id)
end
end
end

View File

@ -0,0 +1,5 @@
- add_to_breadcrumbs s_('ModelRegistry|Model registry'), project_ml_models_path(@model.project)
- breadcrumb_title @model.name
- page_title @model.name
= render(Projects::Ml::ShowMlModelComponent.new(model: @model))

View File

@ -1,8 +0,0 @@
---
name: server_side_frecent_namespaces
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/123554
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/417256
milestone: '16.4'
type: development
group: group::foundations
default_enabled: false

View File

@ -461,7 +461,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
namespace :ml do
resources :experiments, only: [:index, :show, :destroy], controller: 'experiments', param: :iid
resources :candidates, only: [:show, :destroy], controller: 'candidates', param: :iid
resources :models, only: [:index], controller: 'models'
resources :models, only: [:index, :show], controller: 'models', param: :model_id
end
namespace :service_desk do

View File

@ -0,0 +1,7 @@
---
migration_job_name: BackfillHasRemediationsOfVulnerabilityReads
description: Backfills has_remediations column for vulnerability_reads table.
feature_category: database
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/133714
milestone: 16.5
queued_migration_version: 20231011142714

View File

@ -0,0 +1,27 @@
# frozen_string_literal: true
class QueueBackfillHasRemediationsOfVulnerabilityReads < Gitlab::Database::Migration[2.1]
MIGRATION = "BackfillHasRemediationsOfVulnerabilityReads"
DELAY_INTERVAL = 2.minutes
BATCH_SIZE = 10_000
SUB_BATCH_SIZE = 50
restrict_gitlab_migration gitlab_schema: :gitlab_main
disable_ddl_transaction!
def up
queue_batched_background_migration(
MIGRATION,
:vulnerability_reads,
:vulnerability_id,
job_interval: DELAY_INTERVAL,
queued_migration_version: '20231011142714',
batch_size: BATCH_SIZE,
sub_batch_size: SUB_BATCH_SIZE
)
end
def down
delete_batched_background_migration(MIGRATION, :vulnerability_reads, :vulnerability_id, [])
end
end

View File

@ -0,0 +1 @@
c495f8e107e32d4f5f10c4240cbd027e1dfbb5551bff8a0f8c752d5099ef3e05

View File

@ -21,10 +21,10 @@ Get all wiki pages for a given project.
GET /projects/:id/wikis
```
| Attribute | Type | Required | Description |
| --------- | ------- | -------- | --------------------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) |
| `with_content` | boolean | no | Include pages' content |
| Attribute | Type | Required | Description |
| -------------- | -------------- | -------- | ----------- |
| `id` | integer/string | Yes | The ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding). |
| `with_content` | boolean | No | Include pages' content. |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/wikis?with_content=1"
@ -65,12 +65,12 @@ Get a wiki page for a given project.
GET /projects/:id/wikis/:slug
```
| Attribute | Type | Required | Description |
| --------- | ------- | -------- | --------------------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) |
| `slug` | string | yes | URL encoded slug (a unique string) of the wiki page, such as `dir%2Fpage_name` |
| `render_html` | boolean | no | Return the rendered HTML of the wiki page |
| `version` | string | no | Wiki page version SHA |
| Attribute | Type | Required | Description |
| ------------- | -------------- | -------- | ----------- |
| `id` | integer/string | Yes | The ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding). |
| `slug` | string | Yes | URL encoded slug (a unique string) of the wiki page, such as `dir%2Fpage_name`. |
| `render_html` | boolean | No | Return the rendered HTML of the wiki page. |
| `version` | string | No | Wiki page version SHA. |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/wikis/home"
@ -90,18 +90,18 @@ Example response:
## Create a new wiki page
Creates a new wiki page for the given repository with the given title, slug, and content.
Creates a new wiki page for the given repository with the given title, slug and content.
```plaintext
POST /projects/:id/wikis
```
| Attribute | Type | Required | Description |
| ------------- | ------- | -------- | ---------------------------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) |
| `content` | string | yes | The content of the wiki page |
| `title` | string | yes | The title of the wiki page |
| `format` | string | no | The format of the wiki page. Available formats are: `markdown` (default), `rdoc`, `asciidoc` and `org` |
| Attribute | Type | Required | Description |
| ----------| -------------- | -------- | ----------- |
| `id` | integer/string | Yes | The ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding). |
| `content` | string | Yes | The content of the wiki page. |
| `title` | string | Yes | The title of the wiki page. |
| `format` | string | No | The format of the wiki page. Available formats are: `markdown` (default), `rdoc`, `asciidoc` and `org`. |
```shell
curl --data "format=rdoc&title=Hello&content=Hello world" \
@ -128,13 +128,13 @@ Updates an existing wiki page. At least one parameter is required to update the
PUT /projects/:id/wikis/:slug
```
| Attribute | Type | Required | Description |
| --------------- | ------- | --------------------------------- | ------------------------------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) |
| `content` | string | yes if `title` is not provided | The content of the wiki page |
| `title` | string | yes if `content` is not provided | The title of the wiki page |
| `format` | string | no | The format of the wiki page. Available formats are: `markdown` (default), `rdoc`, `asciidoc` and `org` |
| `slug` | string | yes | URL-encoded slug (a unique string) of the wiki page, such as `dir%2Fpage_name` |
| Attribute | Type | Required | Description |
| --------- | ------- | ---------------------------------- | ----------- |
| `id` | integer/string | Yes | The ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding). |
| `content` | string | Yes, if `title` is not provided. | The content of the wiki page. |
| `title` | string | Yes, if `content` is not provided. | The title of the wiki page. |
| `format` | string | No | The format of the wiki page. Available formats are: `markdown` (default), `rdoc`, `asciidoc` and `org`. |
| `slug` | string | Yes | URL-encoded slug (a unique string) of the wiki page, such as `dir%2Fpage_name`. |
```shell
curl --request PUT --data "format=rdoc&content=documentation&title=Docs" \
@ -161,16 +161,16 @@ Deletes a wiki page with a given slug.
DELETE /projects/:id/wikis/:slug
```
| Attribute | Type | Required | Description |
| --------- | ------- | -------- | --------------------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) |
| `slug` | string | yes | URL-encoded slug (a unique string) of the wiki page, such as `dir%2Fpage_name` |
| Attribute | Type | Required | Description |
| --------- | -------------- | -------- | ----------- |
| `id` | integer/string | Yes | The ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding). |
| `slug` | string | Yes | URL-encoded slug (a unique string) of the wiki page, such as `dir%2Fpage_name`. |
```shell
curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/wikis/foo"
```
On success the HTTP status code is `204` and no JSON response is expected.
If successful, a `204 No Content` HTTP response with an empty body is expected.
## Upload an attachment to the wiki repository
@ -181,11 +181,11 @@ Uploads a file to the attachment folder inside the wiki's repository. The
POST /projects/:id/wikis/attachments
```
| Attribute | Type | Required | Description |
| ------------- | ------- | -------- | ---------------------------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) |
| `file` | string | yes | The attachment to be uploaded |
| `branch` | string | no | The name of the branch. Defaults to the wiki repository default branch |
| Attribute | Type | Required | Description |
| --------- | -------------- | -------- | ----------- |
| `id` | integer/string | Yes | The ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding). |
| `file` | string | Yes | The attachment to be uploaded. |
| `branch` | string | No | The name of the branch. Defaults to the wiki repository default branch. |
To upload a file from your file system, use the `--form` argument. This causes
cURL to post data using the header `Content-Type: multipart/form-data`.

View File

@ -139,4 +139,4 @@ TBD
## References
- [GitLab Issue #215511](https://gitlab.com/gitlab-org/gitlab/-/issues/215511)
- [GitLab Epic 11535](https://gitlab.com/groups/gitlab-org/-/epics/11535)

View File

@ -92,21 +92,20 @@ This chart contains several types of items:
AD["diffs~~diff_gutter_avatars.vue"]
AE["ee-diffs~~inline_findings_flag_switcher.vue"]
AF["notes~~noteable_note.vue"]
AG["unknown~~publish_button.vue"]
AH["notes~~note_actions.vue"]
AI["notes~~note_body.vue"]
AJ["notes~~note_header.vue"]
AK["notes~~reply_button.vue"]
AL["notes~~note_awards_list.vue"]
AM["notes~~note_edited_text.vue"]
AN["notes~~note_form.vue"]
AO["vue_shared~~awards_list.vue"]
AP["emoji~~picker.vue"]
AQ["emoji~~emoji_list.vue"]
AG["notes~~note_actions.vue"]
AH["notes~~note_body.vue"]
AI["notes~~note_header.vue"]
AJ["notes~~reply_button.vue"]
AK["notes~~note_awards_list.vue"]
AL["notes~~note_edited_text.vue"]
AM["notes~~note_form.vue"]
AN["vue_shared~~awards_list.vue"]
AO["emoji~~picker.vue"]
AP["emoji~~emoji_list.vue"]
descEmojiVirtualScroll(["Virtual Scroller"])
AR["emoji~~category.vue"]
AS["emoji~emoji_category.vue"]
AT["vue_shared~~markdown_editor.vue"]
AQ["emoji~~category.vue"]
AR["emoji~emoji_category.vue"]
AS["vue_shared~~markdown_editor.vue"]
class codeForFiles,codeForImageDiscussions code;
class codeForTwoUpDiscussions,codeForTwoUpDrafts code;
@ -124,9 +123,7 @@ This chart contains several types of items:
Find in page search
(Cmd/Ctrl+f) is used."|codeForFiles
descVirtualScroller --> codeForFiles
codeForFiles --> B
B --> C
C --> D
codeForFiles --> B --> C --> D
B --> E
%% File view flags cascade
@ -172,10 +169,13 @@ This chart contains several types of items:
P & Q --> S
S --> codeForImageDiscussions
S --> AN
S --> AM
R-->|"Rendered in
note container div"|U & W & V
%% Do not combine this with the "P & Q --> S" statement above
%% The order of these node relationships defines the
%% layout of the graph, and we need it in this order.
R --> S
V --> codeForTwoUpDiscussions
@ -203,11 +203,11 @@ This chart contains several types of items:
%% Draft notes
AB --> AF
AF --> AH & AI & AJ
AH --> AK
AI --> AL & AM & AN
AL --> AO --> AP --> AQ --> descEmojiVirtualScroll --> AR --> AS
AN --> AT
AF --> AG & AH & AI
AG --> AJ
AH --> AK & AL & AM
AK --> AN --> AO --> AP --> descEmojiVirtualScroll --> AQ --> AR
AM --> AS
```
Some of the components are rendered more than others, but the main component is `diff_row.vue`.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

@ -10,9 +10,6 @@ info: For assistance with this tutorial, see https://about.gitlab.com/handbook/p
Follow this tutorial to learn how to use the new left sidebar to navigate the UI.
Provide feedback in
[issue 409005](https://gitlab.com/gitlab-org/gitlab/-/issues/409005).
## Enable the new left sidebar
To view the new sidebar:
@ -81,6 +78,8 @@ Select **Search or go to** and then select **Your work**:
![Your work](img/your_work_v16_4.png)
Then, on the left sidebar, **Your work** is displayed.
## Go to the Admin Area
The Admin Area is also available on the left sidebar when you select **Search or go to**:

View File

@ -12,7 +12,7 @@ GitLab is creating AI-assisted features across our DevSecOps platform. These fea
| Feature | Purpose | Large Language Model | Current availability | Maturity |
|-|-|-|-|-|
| [Suggested Reviewers](project/merge_requests/reviews/index.md#gitlab-duo-suggested-reviewers) | Assists in creating faster and higher-quality reviews by automatically suggesting reviewers for your merge request. | GitLab creates a machine learning model for each project, which is used to generate reviewers <br><br> [View the issue](https://gitlab.com/gitlab-org/modelops/applied-ml/applied-ml-updates/-/issues/10) | SaaS only <br><br> Ultimate tier | [Generally Available (GA)](../policy/experiment-beta-support.md#generally-available-ga) |
| [Code Suggestions](project/repository/code_suggestions/index.md) | Helps you write code more efficiently by viewing code suggestions as you type. | [`code-gecko`](https://cloud.google.com/vertex-ai/docs/generative-ai/model-reference/code-completion) and [`code-bison`](https://cloud.google.com/vertex-ai/docs/generative-ai/model-reference/code-generation) | SaaS <br> Self-managed <br><br> All tiers | [Beta](../policy/experiment-beta-support.md#beta) |
| [Code Suggestions](project/repository/code_suggestions/index.md) | Helps you write code more efficiently by viewing code suggestions as you type. | [`code-gecko`](https://cloud.google.com/vertex-ai/docs/generative-ai/model-reference/code-completion) and [`code-bison`](https://cloud.google.com/vertex-ai/docs/generative-ai/model-reference/code-generation) <br><br> [Anthropic's Claude](https://www.anthropic.com/product) model | SaaS <br> Self-managed <br><br> All tiers | [Beta](../policy/experiment-beta-support.md#beta) |
| [Vulnerability summary](application_security/vulnerabilities/index.md#explaining-a-vulnerability) | Helps you remediate vulnerabilities more efficiently, uplevel your skills, and write more secure code. | [`text-bison`](https://cloud.google.com/vertex-ai/docs/generative-ai/model-reference/text) <br><br> Anthropic's claude model if degraded performance | SaaS only <br><br> Ultimate tier | [Beta](../policy/experiment-beta-support.md#beta) |
| [Code explanation](#explain-code-in-the-web-ui-with-code-explanation) | Helps you understand code by explaining it in English language. | [`codechat-bison`](https://cloud.google.com/vertex-ai/docs/generative-ai/model-reference/code-chat) | SaaS only <br><br> Ultimate tier | [Experiment](../policy/experiment-beta-support.md#experiment) |
| [GitLab Duo Chat](#answer-questions-with-gitlab-duo-chat) | Process and generate text and code in a conversational manner. Helps you quickly identify useful information in large volumes of text in issues, epics, code, and GitLab documentation. | Anthropic's claude model <br><br> OpenAI Embeddings | SaaS only <br><br> Ultimate tier | [Experiment](../policy/experiment-beta-support.md#experiment) |

View File

@ -22,7 +22,7 @@ remote: GitLab: You are attempting to check in one or more files which exceed th
- 257cc5642cb1a054f08cc83f2d943e56fd3ebe99 (123 MiB)
- 5716ca5987cbf97d6bb54920bea6adde242d87e6 (396 MiB)
Please refer to $URL for further information.
Please refer to https://docs.gitlab.com/ee/user/free_user_limit.html for further information.
To https://gitlab.com/group/my-project.git
! [remote rejected] main -> main (pre-receive hook declined)
error: failed to push some refs to 'https://gitlab.com/group/my-project.git'
@ -37,6 +37,11 @@ tree -r | grep <id>
Because Git is not designed to handle large non-text-based data well, you should use [Git LFS](../topics/git/lfs/index.md) for these files.
Git LFS is designed to work with Git to track large files.
## Feedback
If you have any feedback to share about this limit, please do so in
[issue 428188](https://gitlab.com/gitlab-org/gitlab/-/issues/428188).
## Related topics
- [GitLab SaaS Free tier frequently asked questions](https://about.gitlab.com/pricing/faq-efficient-free-tier/).

View File

@ -79,19 +79,8 @@ The following triggers are available for Slack notifications:
| **Wiki page** | A wiki page is created or updated. |
| **Deployment** | A deployment starts or finishes. |
| **Alert** | A new, unique alert is recorded. |
| **[Group mention](#trigger-notifications-for-group-mentions) in public** | A group is mentioned in a public context. |
| **[Group mention](#trigger-notifications-for-group-mentions) in private** | A group is mentioned in a confidential context. |
| [**Vulnerability**](../../application_security/vulnerabilities/index.md) | A new, unique vulnerability is recorded. |
## Trigger notifications for group mentions
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/417751) in GitLab 16.4.
To trigger a [notification event](#triggers-for-slack-notifications) for a group mention, use `@<group_name>` in:
- Issue and merge request descriptions
- Comments on issues, merge requests, and commits
## Troubleshooting
If your Slack integration is not working, start troubleshooting by

View File

@ -23,7 +23,7 @@ Event type | Trigger
[Group member event](#group-member-events) | A user is added or removed from a group, or a user's access level or access expiration date changes.
[Subgroup event](#subgroup-events) | A subgroup is created or removed from a group.
[Feature flag event](#feature-flag-events) | A feature flag is turned on or off.
[Release event](#release-events) | A release is created or updated.
[Release event](#release-events) | A release is created, updated, or deleted.
[Emoji event](#emoji-events) | An emoji reaction is added or removed.
NOTE:

View File

@ -0,0 +1,43 @@
# frozen_string_literal: true
module Gitlab
module BackgroundMigration
# Backfills has_remediations column for vulnerability_reads table.
class BackfillHasRemediationsOfVulnerabilityReads < BatchedMigrationJob
operation_name :set_has_remediations
feature_category :database
UPDATE_SQL = <<~SQL
UPDATE
vulnerability_reads
SET
has_remediations = true
FROM
(%<subquery>s) as sub_query
WHERE
vulnerability_reads.vulnerability_id = sub_query.vulnerability_id
SQL
def perform
each_sub_batch do |sub_batch|
update_query = update_query_for(sub_batch)
connection.execute(update_query)
end
end
private
def update_query_for(sub_batch)
subquery = sub_batch.joins("
INNER JOIN vulnerability_occurrences ON
vulnerability_reads.vulnerability_id = vulnerability_occurrences.vulnerability_id")
.select("vulnerability_reads.vulnerability_id, vulnerability_occurrences.id")
.joins("INNER JOIN vulnerability_findings_remediations ON
vulnerability_occurrences.id = vulnerability_findings_remediations.vulnerability_occurrence_id")
format(UPDATE_SQL, subquery: subquery.to_sql)
end
end
end
end

View File

@ -1210,6 +1210,14 @@ module Gitlab
end
end
def get_file_attributes(revision, file_paths, attributes)
wrapped_gitaly_errors do
gitaly_repository_client
.get_file_attributes(revision, file_paths, attributes)
.attribute_infos
end
end
private
def repository_info_size_megabytes

View File

@ -353,6 +353,13 @@ module Gitlab
)
end
def get_file_attributes(revision, paths, attributes)
request = Gitaly::GetFileAttributesRequest
.new(repository: @gitaly_repo, revision: revision, paths: paths, attributes: attributes)
gitaly_client_call(@repository.storage, :repository_service, :get_file_attributes, request, timeout: GitalyClient.fast_timeout)
end
private
def search_results_from_response(gitaly_response, options = {})

View File

@ -76,7 +76,6 @@ module Gitlab
push_frontend_feature_flag(:source_editor_toolbar)
push_frontend_feature_flag(:vscode_web_ide, current_user)
push_frontend_feature_flag(:unbatch_graphql_queries, current_user)
push_frontend_feature_flag(:server_side_frecent_namespaces, current_user)
# To be removed with https://gitlab.com/gitlab-org/gitlab/-/issues/399248
push_frontend_feature_flag(:remove_monitor_metrics)
push_frontend_feature_flag(:custom_emoji)

View File

@ -233,7 +233,7 @@
"yaml": "^2.0.0-10"
},
"devDependencies": {
"@gitlab/eslint-plugin": "19.1.0",
"@gitlab/eslint-plugin": "19.2.0",
"@gitlab/stylelint-config": "5.0.0",
"@graphql-eslint/eslint-plugin": "3.20.1",
"@originjs/vite-plugin-commonjs": "^1.0.3",

View File

@ -0,0 +1,11 @@
# frozen_string_literal: true
module QA
FactoryBot.define do
factory :merge_request, class: 'QA::Resource::MergeRequest' do
trait :no_preparation do
no_preparation { true }
end
end
end
end

View File

@ -195,8 +195,7 @@ module QA
# the project without ever knowing what is in it.
@file_name = "added_file-00000000.txt"
@source_branch = "large_merge_request"
visit("#{project.web_url}/-/merge_requests/1")
current_url
@web_url = "#{project.web_url}/-/merge_requests/1"
end
# Return subset of fields for comparing merge requests

View File

@ -32,9 +32,7 @@ module QA
it 'sends a merge request event', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/349720' do
Resource::ProjectWebHook.setup(session: session, merge_requests: true) do |webhook, smocker|
Resource::MergeRequest.fabricate_via_api! do |merge_request|
merge_request.project = webhook.project
end
create(:merge_request, project: webhook.project)
expect_web_hook_single_event_success(webhook, smocker, type: 'merge_request')
end

View File

@ -46,11 +46,10 @@ module QA
context 'with associated merge request' do
let!(:source_mr) do
Resource::MergeRequest.fabricate_via_api! do |mr|
mr.project = source_project
mr.api_client = source_admin_api_client
mr.description = "Closes #{source_issue.web_url}"
end
create(:merge_request,
project: source_project,
api_client: source_admin_api_client,
description: "Closes #{source_issue.web_url}")
end
let(:imported_related_mrs) do

View File

@ -17,11 +17,10 @@ module QA
end
let!(:source_mr) do
Resource::MergeRequest.fabricate_via_api! do |mr|
mr.project = source_project
mr.api_client = source_admin_api_client
mr.reviewer_ids = [source_mr_reviewer.id]
end
create(:merge_request,
project: source_project,
api_client: source_admin_api_client,
reviewer_ids: [source_mr_reviewer.id])
end
let!(:mr_reviewer) do

View File

@ -53,10 +53,7 @@ module QA
expect(merge_request).not_to be_nil, "There was a problem creating the merge request"
merge_request = Resource::MergeRequest.fabricate_via_api! do |mr|
mr.project = project
mr.iid = merge_request[:iid]
end
merge_request = create(:merge_request, project: project, iid: merge_request[:iid])
aggregate_failures do
expect(merge_request.state).to eq('opened')
@ -103,10 +100,7 @@ module QA
mr = nil
begin
merge_request = Support::Retrier.retry_until(max_duration: 60, sleep_interval: 5, message: 'The merge request was not merged') do
mr = Resource::MergeRequest.fabricate_via_api! do |mr|
mr.project = project
mr.iid = merge_request[:iid]
end
mr = create(:merge_request, project: project, iid: merge_request[:iid])
next unless mr.state == 'merged'

View File

@ -26,10 +26,7 @@ module QA
expect(merge_request).not_to be_nil, "There was a problem creating the merge request"
merge_request = Resource::MergeRequest.fabricate_via_api! do |mr|
mr.project = project
mr.iid = merge_request[:iid]
end.merge_via_api!
merge_request = create(:merge_request, project: project, iid: merge_request[:iid]).merge_via_api!
expect(merge_request[:state]).to eq('merged')

View File

@ -36,10 +36,7 @@ module QA
expect(merge_request).not_to be_nil, "There was a problem creating the merge request"
expect(merge_request[:target_branch]).to eq(target_branch)
merge_request = Resource::MergeRequest.fabricate_via_api! do |mr|
mr.project = project
mr.iid = merge_request[:iid]
end.merge_via_api!
merge_request = create(:merge_request, project: project, iid: merge_request[:iid]).merge_via_api!
expect(merge_request[:state]).to eq('merged')
end

View File

@ -32,10 +32,7 @@ module QA
expect(merge_request[:description]).to eq(description)
end
merge_request = Resource::MergeRequest.fabricate_via_api! do |mr|
mr.project = project
mr.iid = merge_request[:iid]
end.merge_via_api!
merge_request = create(:merge_request, project: project, iid: merge_request[:iid]).merge_via_api!
expect(merge_request[:state]).to eq('merged')
end

View File

@ -4,13 +4,7 @@ module QA
RSpec.describe 'Create' do
describe 'Cherry picking from a merge request', :reliable, product_group: :code_review do
let(:project) { create(:project, :with_readme) }
let(:feature_mr) do
Resource::MergeRequest.fabricate_via_api! do |merge_request|
merge_request.project = project
merge_request.target_branch = 'development'
merge_request.target_new_branch = true
end
end
let(:feature_mr) { create(:merge_request, project: project, target_branch: 'development') }
before do
Flow::Login.sign_in

View File

@ -48,12 +48,11 @@ module QA
QA::Runtime::Logger.info("Transient bug test - Trial #{i + 1}") if transient_test
# Create a merge request to trigger pipeline
merge_request = Resource::MergeRequest.fabricate_via_api! do |merge_request|
merge_request.project = project
merge_request.description = Faker::Lorem.sentence
merge_request.target_new_branch = false
merge_request.source_branch = "mr-test-#{SecureRandom.hex(6)}-#{i + 1}"
end
merge_request = create(:project,
project: project,
description: Faker::Lorem.sentence,
target_new_branch: false,
source_branch: "mr-test-#{SecureRandom.hex(6)}-#{i + 1}")
# Load the page so that the browser is as prepared as possible to display the pipeline in progress when we
# start it.

View File

@ -3,7 +3,7 @@
module QA
RSpec.describe 'Create' do
describe 'Merge request rebasing', product_group: :code_review do
let(:merge_request) { Resource::MergeRequest.fabricate_via_api! }
let(:merge_request) { create(:merge_request) }
before do
Flow::Login.sign_in

View File

@ -3,12 +3,7 @@
module QA
RSpec.describe 'Create' do
describe 'Merged merge request', :requires_admin, product_group: :code_review do
let(:project) { create(:project) }
let(:revertible_merge_request) do
Resource::MergeRequest.fabricate_via_api! do |merge_request|
merge_request.project = project
end
end
let(:revertible_merge_request) { create(:merge_request) }
before do
Flow::Login.sign_in

View File

@ -4,12 +4,7 @@ module QA
RSpec.describe 'Create' do
describe 'Merge request squashing', :reliable, product_group: :code_review do
let(:project) { create(:project, name: 'squash-before-merge') }
let(:merge_request) do
Resource::MergeRequest.fabricate_via_api! do |merge_request|
merge_request.project = project
merge_request.title = 'Squashing commits'
end
end
let(:merge_request) { create(:merge_request, project: project, title: 'Squashing commits') }
before do
Flow::Login.sign_in

View File

@ -5,14 +5,13 @@ module QA
describe 'Merge request batch suggestions' do
let(:project) { create(:project, name: 'batch-suggestions-project') }
let(:merge_request) do
Resource::MergeRequest.fabricate_via_api! do |merge_request|
merge_request.project = project
merge_request.title = 'Needs some suggestions'
merge_request.description = '... so please add them.'
merge_request.file_content = File.read(
create(:merge_request,
project: project,
title: 'Needs some suggestions',
description: '... so please add them.',
file_content: File.read(
Runtime::Path.fixture('metrics_dashboards', 'templating.yml')
)
end
))
end
let(:dev_user) do

View File

@ -6,14 +6,13 @@ module QA
let(:commit_message) { 'Applying suggested change for testing purposes.' }
let(:project) { create(:project, name: 'mr-suggestions-project') }
let(:merge_request) do
Resource::MergeRequest.fabricate_via_api! do |merge_request|
merge_request.project = project
merge_request.title = 'Needs some suggestions'
merge_request.description = '... so please add them.'
merge_request.file_content = File.read(
create(:merge_request,
project: project,
title: 'Needs some suggestions',
description: '... so please add them.',
file_content: File.read(
Runtime::Path.fixture('metrics_dashboards', 'templating.yml')
)
end
))
end
let(:dev_user) do

View File

@ -4,10 +4,7 @@ module QA
RSpec.describe 'Create' do
describe 'Download merge request patch and diff', :reliable, :requires_admin, product_group: :code_review do
let(:merge_request) do
Resource::MergeRequest.fabricate_via_api! do |merge_request|
merge_request.title = 'This is a merge request'
merge_request.description = '... for downloading patches and diffs'
end
create(:merge_request, title: 'This is a merge request', description: '... for downloading patches and diffs')
end
before do

View File

@ -36,12 +36,7 @@ module QA
end
let(:merge_request) do
Resource::MergeRequest.fabricate_via_api! do |mr|
mr.source = source
mr.project = project
mr.source_branch = 'new-mr'
mr.target_new_branch = false
end
create(:merge_request, source: source, project: project, source_branch: 'new-mr', target_new_branch: false)
end
before do

View File

@ -14,11 +14,7 @@ module QA
let(:project) { create(:project, :with_readme, name: 'review-mr-spec-project') }
let(:merge_request) do
Resource::MergeRequest.fabricate_via_api! do |mr|
mr.file_name = new_file
mr.file_content = original_text
mr.project = project
end
create(:merge_request, file_name: new_file, file_content: original_text, project: project)
end
before do

View File

@ -99,14 +99,13 @@ module QA
end
def create_merge_request(api_client)
Resource::MergeRequest.fabricate_via_api! do |merge_request|
merge_request.api_client = api_client
merge_request.project = project
merge_request.description = Faker::Lorem.sentence
merge_request.target_new_branch = false
merge_request.file_name = Faker::File.unique.file_name
merge_request.file_content = Faker::Lorem.sentence
end
create(:merge_request,
api_client: api_client,
project: project,
description: Faker::Lorem.sentence,
target_new_branch: false,
file_name: Faker::File.unique.file_name,
file_content: Faker::Lorem.sentence)
end
def go_to_pipeline_job(user)

View File

@ -45,13 +45,12 @@ module QA
end
let(:merge_request) do
Resource::MergeRequest.fabricate_via_api! do |merge_request|
merge_request.project = project
merge_request.description = Faker::Lorem.sentence
merge_request.target_new_branch = false
merge_request.file_name = 'custom_file.txt'
merge_request.file_content = Faker::Lorem.sentence
end
create(:merge_request,
project: project,
description: Faker::Lorem.sentence,
target_new_branch: false,
file_name: 'custom_file.txt',
file_content: Faker::Lorem.sentence)
end
before do

View File

@ -25,12 +25,7 @@ module QA
create(:project, :with_readme, name: 'project-for-tags', api_client: followed_user_api_client, group: group)
end
let(:merge_request) do
Resource::MergeRequest.fabricate_via_api! do |mr|
mr.project = project
mr.api_client = followed_user_api_client
end
end
let(:merge_request) { create(:merge_request, project: project, api_client: followed_user_api_client) }
let(:issue) { create(:issue, project: project, api_client: followed_user_api_client) }

View File

@ -0,0 +1,30 @@
# frozen_string_literal: true
require "spec_helper"
RSpec.describe Projects::Ml::ShowMlModelComponent, type: :component, feature_category: :mlops do
let_it_be(:project) { build_stubbed(:project) }
let_it_be(:model1) { build_stubbed(:ml_models, :with_latest_version_and_package, project: project) }
subject(:component) do
described_class.new(model: model1)
end
describe 'rendered' do
before do
render_inline component
end
it 'renders element with view_model' do
element = page.find("#js-mount-show-ml-model")
expect(Gitlab::Json.parse(element['data-view-model'])).to eq({
'model' => {
'id' => model1.id,
'name' => model1.name,
'path' => "/#{project.full_path}/-/ml/models/#{model1.id}"
}
})
end
end
end

View File

@ -0,0 +1,15 @@
import { shallowMount } from '@vue/test-utils';
import { ShowMlModel } from '~/ml/model_registry/apps';
import { MODEL } from '../mock_data';
let wrapper;
const createWrapper = () => {
wrapper = shallowMount(ShowMlModel, { propsData: { model: MODEL } });
};
describe('ShowMlModel', () => {
beforeEach(() => createWrapper());
it('renders the app', () => {
expect(wrapper.text()).toContain(MODEL.name);
});
});

View File

@ -0,0 +1 @@
export const MODEL = { name: 'blah' };

View File

@ -11,7 +11,7 @@ import axios from '~/lib/utils/axios_utils';
import { useLocalStorageSpy } from 'helpers/local_storage_helper';
import AccessorUtilities from '~/lib/utils/accessor';
import { FREQUENT_ITEMS, FIFTEEN_MINUTES_IN_MS } from '~/frequent_items/constants';
import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
import { HTTP_STATUS_OK, HTTP_STATUS_INTERNAL_SERVER_ERROR } from '~/lib/utils/http_status';
import waitForPromises from 'helpers/wait_for_promises';
import { unsortedFrequentItems, sortedFrequentItems } from '../frequent_items/mock_data';
import { cachedFrequentProjects } from './mock_data';
@ -58,7 +58,6 @@ describe('Super sidebar utils spec', () => {
const storageKey = `${username}/frequent-${context.namespace}`;
beforeEach(() => {
gon.features = { serverSideFrecentNamespaces: true };
axiosMock = new MockAdapter(axios);
axiosMock.onPost(trackVisitsPath).reply(HTTP_STATUS_OK);
});
@ -99,12 +98,12 @@ describe('Super sidebar utils spec', () => {
expect(axiosMock.history.post[0].url).toBe(trackVisitsPath);
});
it('does not send a POST request when the serverSideFrecentNamespaces feature flag is disabled', async () => {
gon.features = { serverSideFrecentNamespaces: false };
it('logs an error to Sentry when the request fails', async () => {
axiosMock.onPost(trackVisitsPath).reply(HTTP_STATUS_INTERNAL_SERVER_ERROR);
trackContextAccess(username, context, trackVisitsPath);
await waitForPromises();
expect(axiosMock.history.post).toHaveLength(0);
expect(Sentry.captureException).toHaveBeenCalled();
});
it('updates existing item frequency/access time if it was persisted to the local storage over 15 minutes ago', () => {

View File

@ -1,4 +1,4 @@
import { GlLabel, GlIcon } from '@gitlab/ui';
import { GlLabel, GlIcon, GlLink } from '@gitlab/ui';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
@ -33,7 +33,7 @@ describe('WorkItemLinkChildContents', () => {
const findStatusIconComponent = () =>
wrapper.findByTestId('item-status-icon').findComponent(GlIcon);
const findConfidentialIconComponent = () => wrapper.findByTestId('confidential-icon');
const findTitleEl = () => wrapper.findByTestId('item-title');
const findTitleEl = () => wrapper.findComponent(GlLink);
const findStatusTooltipComponent = () => wrapper.findComponent(RichTimestampTooltip);
const findMetadataComponent = () => wrapper.findComponent(WorkItemLinkChildMetadata);
const findAllLabels = () => wrapper.findAllComponents(GlLabel);
@ -46,7 +46,6 @@ describe('WorkItemLinkChildContents', () => {
propsData: {
canUpdate,
childItem,
childPath: '/gitlab-org/gitlab-test/-/work_items/4',
},
});
};

View File

@ -62,9 +62,6 @@ describe('WorkItemLinkChild', () => {
[getWorkItemTreeQuery, getWorkItemTreeQueryHandler],
[updateWorkItemMutation, mutationChangeParentHandler],
]),
provide: {
fullPath: 'gitlab-org/gitlab-test',
},
propsData: {
canUpdate,
issuableGid,
@ -93,26 +90,9 @@ describe('WorkItemLinkChild', () => {
expect(findWorkItemLinkChildContents().props()).toEqual({
childItem: workItemObjectiveWithChild,
canUpdate: true,
childPath: '/gitlab-org/gitlab-test/-/work_items/12',
showTaskIcon: false,
});
});
describe('with relative instance', () => {
beforeEach(() => {
window.gon = { relative_url_root: '/test' };
createComponent({
childItem: workItemObjectiveWithChild,
workItemType: WORK_ITEM_TYPE_VALUE_OBJECTIVE,
});
});
it('adds the relative url to child path value', () => {
expect(findWorkItemLinkChildContents().props('childPath')).toBe(
'/test/gitlab-org/gitlab-test/-/work_items/12',
);
});
});
});
describe('nested children', () => {

View File

@ -22,7 +22,6 @@ exports[`WorkItemRelationshipList renders linked item list 1`] = `
<work-item-link-child-contents-stub
canupdate="true"
childitem="[object Object]"
childpath="/test-project-path/-/work_items/83"
showtaskicon="true"
/>
</li>

View File

@ -14,7 +14,6 @@ describe('WorkItemRelationshipList', () => {
linkedItems,
heading,
canUpdate,
workItemFullPath: 'test-project-path',
},
});
};
@ -35,7 +34,6 @@ describe('WorkItemRelationshipList', () => {
expect(findWorkItemLinkChildContents().props()).toMatchObject({
childItem: mockLinkedItems[0].workItem,
canUpdate: true,
childPath: '/test-project-path/-/work_items/83',
showTaskIcon: true,
});
});

View File

@ -489,6 +489,7 @@ export const mockBlockingLinkedItem = {
state: 'OPEN',
createdAt: '2023-03-28T10:50:16Z',
closedAt: null,
webUrl: '/gitlab-org/gitlab-test/-/work_items/83',
widgets: [],
__typename: 'WorkItem',
},
@ -521,6 +522,7 @@ export const mockLinkedItems = {
state: 'OPEN',
createdAt: '2023-03-28T10:50:16Z',
closedAt: null,
webUrl: '/gitlab-org/gitlab-test/-/work_items/83',
widgets: [],
__typename: 'WorkItem',
},
@ -543,6 +545,7 @@ export const mockLinkedItems = {
state: 'OPEN',
createdAt: '2023-03-28T10:50:16Z',
closedAt: null,
webUrl: '/gitlab-org/gitlab-test/-/work_items/55',
widgets: [],
__typename: 'WorkItem',
},
@ -565,6 +568,7 @@ export const mockLinkedItems = {
state: 'OPEN',
createdAt: '2023-03-28T10:50:16Z',
closedAt: null,
webUrl: '/gitlab-org/gitlab-test/-/work_items/56',
widgets: [],
__typename: 'WorkItem',
},
@ -1121,6 +1125,7 @@ export const workItemTask = {
confidential: false,
createdAt: '2022-08-03T12:41:54Z',
closedAt: null,
webUrl: '/gitlab-org/gitlab-test/-/work_items/4',
widgets: [],
__typename: 'WorkItem',
};

View File

@ -1,4 +1,4 @@
import { autocompleteDataSources, markdownPreviewPath, workItemPath } from '~/work_items/utils';
import { autocompleteDataSources, markdownPreviewPath } from '~/work_items/utils';
describe('autocompleteDataSources', () => {
beforeEach(() => {
@ -25,14 +25,3 @@ describe('markdownPreviewPath', () => {
);
});
});
describe('workItemPath', () => {
it('returns corrrect data sources', () => {
expect(workItemPath('project/group', '2')).toEqual('/project/group/-/work_items/2');
});
it('returns corrrect data sources with relative url root', () => {
gon.relative_url_root = '/foobar';
expect(workItemPath('project/group', '2')).toEqual('/foobar/project/group/-/work_items/2');
});
});

View File

@ -637,4 +637,27 @@ RSpec.describe DiffHelper, feature_category: :code_review_workflow do
end
end
end
describe '#submodule_diff_compare_link' do
context 'when the diff includes submodule changes' do
it 'generates a link to compare a diff for a submodule' do
allow(helper).to receive(:submodule_links).and_return(
Gitlab::SubmoduleLinks::Urls.new(nil, nil, '/comparison-path')
)
output = helper.submodule_diff_compare_link(diff_file)
expect(output).to match(%r{href="/comparison-path"})
expect(output).to match(
%r{Compare <span class="commit-sha">5b812ff1</span>...<span class="commit-sha">7e3e39eb</span>}
)
end
end
context 'when the diff does not include submodule changes' do
it 'returns an empty string' do
output = helper.submodule_diff_compare_link(diff_file)
expect(output).to eq('')
end
end
end
end

View File

@ -0,0 +1,136 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::BackgroundMigration::BackfillHasRemediationsOfVulnerabilityReads,
feature_category: :database do
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:users) { table(:users) }
let(:scanners) { table(:vulnerability_scanners) }
let(:vulnerabilities) { table(:vulnerabilities) }
let(:vulnerability_reads) { table(:vulnerability_reads) }
let(:namespace) { namespaces.create!(name: 'user', path: 'user') }
let(:project) { projects.create!(namespace_id: namespace.id, project_namespace_id: namespace.id) }
let(:user) { users.create!(username: 'john_doe', email: 'johndoe@gitlab.com', projects_limit: 10) }
let(:scanner) { scanners.create!(project_id: project.id, external_id: 'external_id', name: 'Test Scanner') }
let(:vulnerability_1) { create_vulnerability(title: 'vulnerability 1') }
let(:vulnerability_2) { create_vulnerability(title: 'vulnerability 2') }
let!(:vulnerability_read_1) { create_vulnerability_read(vulnerability_id: vulnerability_1.id) }
let!(:vulnerability_read_2) { create_vulnerability_read(vulnerability_id: vulnerability_2.id) }
let(:vulnerability_findings) { table(:vulnerability_occurrences) }
let(:vulnerability_findings_remediations) { table(:vulnerability_findings_remediations) }
let(:vulnerability_remediations) { table(:vulnerability_remediations) }
let(:vulnerability_identifiers) { table(:vulnerability_identifiers) }
subject(:perform_migration) do
described_class.new(
start_id: vulnerability_reads.first.vulnerability_id,
end_id: vulnerability_reads.last.vulnerability_id,
batch_table: :vulnerability_reads,
batch_column: :vulnerability_id,
sub_batch_size: vulnerability_reads.count,
pause_ms: 0,
connection: ActiveRecord::Base.connection
).perform
end
it 'updates vulnerability_reads records which has remediations' do
vuln_remediation = create_remediation
vuln_finding = create_finding(vulnerability_id: vulnerability_1.id)
vulnerability_findings_remediations.create!(
vulnerability_occurrence_id: vuln_finding.id,
vulnerability_remediation_id: vuln_remediation.id
)
expect { perform_migration }.to change { vulnerability_read_1.reload.has_remediations }.from(false).to(true)
.and not_change { vulnerability_read_2.reload.has_remediations }.from(false)
end
it 'does not modify has_remediations of vulnerabilities which do not have remediations' do
expect { perform_migration }.to not_change { vulnerability_read_1.reload.has_remediations }.from(false)
.and not_change { vulnerability_read_2.reload.has_remediations }.from(false)
end
private
def create_vulnerability(overrides = {})
attrs = {
project_id: project.id,
author_id: user.id,
title: 'test',
severity: 1,
confidence: 1,
report_type: 1
}.merge(overrides)
vulnerabilities.create!(attrs)
end
def create_vulnerability_read(overrides = {})
attrs = {
project_id: project.id,
vulnerability_id: 1,
scanner_id: scanner.id,
severity: 1,
report_type: 1,
state: 1,
uuid: SecureRandom.uuid
}.merge(overrides)
vulnerability_reads.create!(attrs)
end
def create_finding(overrides = {})
attrs = {
project_id: project.id,
scanner_id: scanner.id,
severity: 5, # medium
confidence: 2, # unknown,
report_type: 99, # generic
primary_identifier_id: create_identifier.id,
project_fingerprint: SecureRandom.hex(20),
location_fingerprint: SecureRandom.hex(20),
uuid: SecureRandom.uuid,
name: "CVE-2018-1234",
raw_metadata: "{}",
metadata_version: "test:1.0"
}.merge(overrides)
vulnerability_findings.create!(attrs)
end
def create_remediation(overrides = {})
remediation_hash = { summary: 'summary', diff: "ZGlmZiAtLWdp" }
attrs = {
project_id: project.id,
summary: remediation_hash[:summary],
checksum: checksum(remediation_hash[:diff]),
file: Tempfile.new.path
}.merge(overrides)
vulnerability_remediations.create!(attrs)
end
def create_identifier(overrides = {})
attrs = {
project_id: project.id,
external_id: "CVE-2018-1234",
external_type: "CVE",
name: "CVE-2018-1234",
fingerprint: SecureRandom.hex(20)
}.merge(overrides)
vulnerability_identifiers.create!(attrs)
end
def checksum(value)
sha = Digest::SHA256.hexdigest(value)
Gitlab::Database::ShaAttribute.new.serialize(sha)
end
end

View File

@ -2780,4 +2780,14 @@ RSpec.describe Gitlab::Git::Repository, feature_category: :source_code_managemen
end
end
end
describe '#get_file_attributes' do
let(:rev) { 'master' }
let(:paths) { ['file.txt'] }
let(:attrs) { ['text'] }
it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::RepositoryService, :get_file_attributes do
subject { repository.get_file_attributes(rev, paths, attrs) }
end
end
end

View File

@ -440,4 +440,19 @@ RSpec.describe Gitlab::GitalyClient::RepositoryService, feature_category: :gital
client.object_pool
end
end
describe '#get_file_attributes' do
let(:rev) { 'master' }
let(:paths) { ['file.txt'] }
let(:attrs) { ['text'] }
it 'sends a get_file_attributes message' do
expect_any_instance_of(Gitaly::RepositoryService::Stub)
.to receive(:get_file_attributes)
.with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
.and_call_original
expect(client.get_file_attributes(rev, paths, attrs)).to be_a Gitaly::GetFileAttributesResponse
end
end
end

View File

@ -0,0 +1,26 @@
# frozen_string_literal: true
require 'spec_helper'
require_migration!
RSpec.describe QueueBackfillHasRemediationsOfVulnerabilityReads, feature_category: :database do
let!(:batched_migration) { described_class::MIGRATION }
it 'schedules a new batched migration' do
reversible_migration do |migration|
migration.before -> {
expect(batched_migration).not_to have_scheduled_batched_migration
}
migration.after -> {
expect(batched_migration).to have_scheduled_batched_migration(
table_name: :vulnerability_reads,
column_name: :vulnerability_id,
interval: described_class::DELAY_INTERVAL,
batch_size: described_class::BATCH_SIZE,
sub_batch_size: described_class::SUB_BATCH_SIZE
)
}
end
end
end

View File

@ -118,4 +118,27 @@ RSpec.describe Ml::Model, feature_category: :mlops do
end
end
end
describe '#by_project_and_id' do
let(:id) { existing_model.id }
let(:project_id) { existing_model.project.id }
subject { described_class.by_project_id_and_id(project_id, id) }
context 'if exists' do
it { is_expected.to eq(existing_model) }
end
context 'if id has no match' do
let(:id) { non_existing_record_id }
it { is_expected.to be(nil) }
end
context 'if project id does not match' do
let(:project_id) { non_existing_record_id }
it { is_expected.to be(nil) }
end
end
end

View File

@ -3981,4 +3981,61 @@ RSpec.describe Repository, feature_category: :source_code_management do
end
end
end
describe '#get_file_attributes' do
let(:project) do
create(:project, :custom_repo, files: {
'.gitattributes' => gitattr_content,
'file1.txt' => 'test content'
})
end
let(:gitattr_content) { '' }
let(:rev) { 'master' }
let(:paths) { ['file1.txt', 'README'] }
let(:attrs) { %w[text diff] }
subject(:file_attributes) { repository.get_file_attributes(rev, paths, attrs) }
context 'when the given attributes are defined' do
let(:gitattr_content) { "* -text\n*.txt text\n*.txt diff" }
it 'returns expected attributes' do
expect(file_attributes.count).to eq 3
expect(file_attributes[0]).to eq({ path: 'file1.txt', attribute: 'text', value: 'set' })
expect(file_attributes[1]).to eq({ path: 'file1.txt', attribute: 'diff', value: 'set' })
expect(file_attributes[2]).to eq({ path: 'README', attribute: 'text', value: 'unset' })
end
end
context 'when the attribute is not defined for a given file' do
let(:gitattr_content) { "*.txt text" }
let(:rev) { 'master' }
let(:paths) { ['README'] }
let(:attrs) { ['text'] }
it 'returns an empty array' do
expect(file_attributes).to eq []
end
end
context 'when revision is an empty string' do
let(:rev) { '' }
it { expect { file_attributes }.to raise_error(ArgumentError) }
end
context 'when paths list is empty' do
let(:paths) { [] }
it { expect { file_attributes }.to raise_error(ArgumentError) }
end
context 'when attributes list is empty' do
let(:attrs) { [] }
it { expect { file_attributes }.to raise_error(ArgumentError) }
end
end
end

View File

@ -40,4 +40,10 @@ RSpec.describe Ml::ModelPresenter, feature_category: :mlops do
it { is_expected.to eq("/#{project.full_path}/-/packages/#{model.latest_version.package_id}") }
end
end
describe '#path' do
subject { model1.present.path }
it { is_expected.to eq("/#{project.full_path}/-/ml/models/#{model1.id}") }
end
end

View File

@ -80,9 +80,55 @@ RSpec.describe Projects::Ml::ModelsController, feature_category: :mlops do
end
end
describe 'show' do
let(:model_id) { model1.id }
let(:request_project) { model1.project }
subject(:show_request) do
show_model
response
end
before do
show_request
end
it 'renders the template' do
is_expected.to render_template('projects/ml/models/show')
end
it 'fetches the correct model' do
show_request
expect(assigns(:model)).to eq(model1)
end
context 'when model id does not exist' do
let(:model_id) { non_existing_record_id }
it { is_expected.to have_gitlab_http_status(:not_found) }
end
context 'when model project does not match project id' do
let(:request_project) { model_in_different_project.project }
it { is_expected.to have_gitlab_http_status(:not_found) }
end
context 'when user does not have access' do
let(:model_registry_enabled) { false }
it { is_expected.to have_gitlab_http_status(:not_found) }
end
end
private
def list_models(new_params = nil)
get project_ml_models_path(project), params: new_params || params
end
def show_model
get project_ml_model_path(request_project, model_id)
end
end

View File

@ -19,23 +19,11 @@ RSpec.describe Users::NamespaceVisitsController, type: :request, feature_categor
context "when user is signed-in" do
let_it_be(:user) { create(:user) }
let(:server_side_frecent_namespaces) { true }
before do
stub_feature_flags(server_side_frecent_namespaces: server_side_frecent_namespaces)
sign_in(user)
end
context "when the server_side_frecent_namespaces feature flag is disabled" do
let(:server_side_frecent_namespaces) { false }
it 'throws an error 302' do
subject
expect(response).to have_gitlab_http_status(:not_found)
end
end
context "when entity type is not provided" do
let_it_be(:request_params) { { id: '1' } }

View File

@ -1236,10 +1236,10 @@
core-js "^3.29.1"
mitt "^3.0.1"
"@gitlab/eslint-plugin@19.1.0":
version "19.1.0"
resolved "https://registry.yarnpkg.com/@gitlab/eslint-plugin/-/eslint-plugin-19.1.0.tgz#001ea5af06b236c7f38445880a322a93350d690e"
integrity sha512-qLGfIXURiSPWIsK53ZN8TWNwmWkI0CWqWi3BokKZaW3gwmpWqnalV9vychs0mwou/hXI4zwUVR13m3MkvojCgg==
"@gitlab/eslint-plugin@19.2.0":
version "19.2.0"
resolved "https://registry.yarnpkg.com/@gitlab/eslint-plugin/-/eslint-plugin-19.2.0.tgz#05f07a873af18c85b5668316409cd39256dc141f"
integrity sha512-6cTJoBHWZriOknaWwLzIpYKVX4ivCzO3MXKfXm310zHmtt1UK7tmq3JsyaS7Niw6/M19C2csgL+LedRJ88uZ0w==
dependencies:
eslint-config-airbnb-base "^15.0.0"
eslint-config-prettier "^6.10.0"