Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
5fcd4e5fbc
commit
f5ea25c365
|
|
@ -0,0 +1,3 @@
|
|||
import ShowMlModel from './show_ml_model.vue';
|
||||
|
||||
export { ShowMlModel };
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
<script>
|
||||
export default {
|
||||
name: 'ShowMlModelApp',
|
||||
components: {},
|
||||
props: {
|
||||
model: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>{{ model.name }}</div>
|
||||
</template>
|
||||
|
|
@ -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);
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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')"
|
||||
|
|
|
|||
|
|
@ -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)"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -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)"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -120,6 +120,7 @@ fragment WorkItemWidgets on WorkItemWidget {
|
|||
state
|
||||
createdAt
|
||||
closedAt
|
||||
webUrl
|
||||
widgets {
|
||||
...WorkItemMetadataWidgets
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
#js-mount-show-ml-model{ data: { view_model: view_model } }
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1 @@
|
|||
c495f8e107e32d4f5f10c4240cbd027e1dfbb5551bff8a0f8c752d5099ef3e05
|
||||
|
|
@ -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`.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 |
|
|
@ -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**:
|
|||
|
||||

|
||||
|
||||
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**:
|
||||
|
|
|
|||
|
|
@ -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) |
|
||||
|
|
|
|||
|
|
@ -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/).
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 = {})
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1 @@
|
|||
export const MODEL = { name: 'blah' };
|
||||
|
|
@ -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', () => {
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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', () => {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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' } }
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Reference in New Issue