Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
07959a9d0d
commit
cbd6e780ec
|
|
@ -254,6 +254,10 @@ variables:
|
|||
BUILD_ASSETS_IMAGE: "true" # Set it to "false" to disable assets image building, used in `build-assets-image`
|
||||
SIMPLECOV: "true"
|
||||
|
||||
# Temporary variable to enable new CSS compiler
|
||||
# See: https://gitlab.com/gitlab-org/gitlab/-/issues/438278
|
||||
USE_NEW_CSS_PIPELINE: "true"
|
||||
|
||||
include:
|
||||
- local: .gitlab/ci/_skip.yml
|
||||
rules:
|
||||
|
|
|
|||
|
|
@ -10,9 +10,6 @@ export const BV_SHOW_TOOLTIP = 'bv::show::tooltip';
|
|||
export const BV_DROPDOWN_SHOW = 'bv::dropdown::show';
|
||||
export const BV_DROPDOWN_HIDE = 'bv::dropdown::hide';
|
||||
|
||||
export const DEFAULT_TH_CLASSES =
|
||||
'gl-bg-transparent! gl-border-b-solid! gl-border-b-gray-100! gl-p-5! gl-border-b-1!';
|
||||
|
||||
// We set the drawer's z-index to 252 to clear flash messages that might
|
||||
// be displayed in the page and that have a z-index of 251.
|
||||
export const DRAWER_Z_INDEX = 252;
|
||||
|
|
|
|||
|
|
@ -1,14 +1,4 @@
|
|||
import { convertToSnakeCase, convertToCamelCase } from '~/lib/utils/text_utility';
|
||||
import { DEFAULT_TH_CLASSES } from './constants';
|
||||
|
||||
/**
|
||||
* Deprecated: use thWidthPercent instead
|
||||
* Generates the table header classes to be used for GlTable fields.
|
||||
*
|
||||
* @param {Number} width - The column width as a percentage.
|
||||
* @returns {String} The classes to be used in GlTable fields object.
|
||||
*/
|
||||
export const thWidthClass = (width) => `gl-w-${width}p ${DEFAULT_TH_CLASSES}`;
|
||||
|
||||
/**
|
||||
* Generates the table header class for width to be used for GlTable fields.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
<script>
|
||||
import { GlIcon, GlLink, GlCard } from '@gitlab/ui';
|
||||
import { numberToMetricPrefix } from '~/lib/utils/number_utils';
|
||||
import { __ } from '~/locale';
|
||||
|
||||
export default {
|
||||
|
|
@ -16,7 +15,7 @@ export default {
|
|||
required: true,
|
||||
},
|
||||
count: {
|
||||
type: Number,
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
linkHref: {
|
||||
|
|
@ -29,11 +28,6 @@ export default {
|
|||
default: __('View all'),
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
formattedCount() {
|
||||
return numberToMetricPrefix(this.count, 0);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
@ -48,7 +42,7 @@ export default {
|
|||
</div>
|
||||
<span
|
||||
class="gl-font-size-h-display gl-font-weight-bold gl-line-height-ratio-1000 gl-mt-2 gl-display-block"
|
||||
>{{ formattedCount }}</span
|
||||
>{{ count }}</span
|
||||
>
|
||||
</gl-card>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ export const SCOPE_NOTES = 'notes';
|
|||
export const SCOPE_COMMITS = 'commits';
|
||||
export const SCOPE_MILESTONES = 'milestones';
|
||||
export const SCOPE_WIKI_BLOBS = 'wiki_blobs';
|
||||
export const SCOPE_EPICS = 'epics';
|
||||
export const SCOPE_USERS = 'users';
|
||||
|
||||
export const LABEL_DEFAULT_CLASSES = [
|
||||
'gl-display-flex',
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ import {
|
|||
ADVANCED_SEARCH_TYPE,
|
||||
BASIC_SEARCH_TYPE,
|
||||
SEARCH_LEVEL_PROJECT,
|
||||
SEARCH_LEVEL_GLOBAL,
|
||||
SEARCH_LEVEL_GROUP,
|
||||
} from '~/search/store/constants';
|
||||
import {
|
||||
ZOEKT_HELP_PAGE,
|
||||
|
|
@ -17,6 +19,8 @@ import {
|
|||
ZOEKT_HELP_PAGE_SYNTAX_ANCHOR,
|
||||
} from '../constants';
|
||||
|
||||
import { SCOPE_BLOB } from '../../sidebar/constants';
|
||||
|
||||
export default {
|
||||
name: 'SearchTypeIndicator',
|
||||
directives: {
|
||||
|
|
@ -40,7 +44,7 @@ export default {
|
|||
GlLink,
|
||||
},
|
||||
computed: {
|
||||
...mapState(['searchType', 'defaultBranchName', 'query', 'searchLevel']),
|
||||
...mapState(['searchType', 'defaultBranchName', 'query', 'searchLevel', 'query']),
|
||||
zoektHelpUrl() {
|
||||
return helpPagePath(ZOEKT_HELP_PAGE);
|
||||
},
|
||||
|
|
@ -58,17 +62,39 @@ export default {
|
|||
});
|
||||
},
|
||||
isZoekt() {
|
||||
return this.searchType === ZOEKT_SEARCH_TYPE;
|
||||
return this.searchType === ZOEKT_SEARCH_TYPE && this.query.scope === SCOPE_BLOB;
|
||||
},
|
||||
isAdvancedSearch() {
|
||||
return this.searchType === ADVANCED_SEARCH_TYPE;
|
||||
return (
|
||||
this.searchType === ADVANCED_SEARCH_TYPE ||
|
||||
(this.searchType === ZOEKT_SEARCH_TYPE && this.query.scope !== SCOPE_BLOB)
|
||||
);
|
||||
},
|
||||
isEnabled() {
|
||||
if (this.searchLevel !== SEARCH_LEVEL_PROJECT) {
|
||||
return true;
|
||||
searchTypeTestId() {
|
||||
if (this.isZoekt) {
|
||||
return ZOEKT_SEARCH_TYPE;
|
||||
}
|
||||
if (this.isAdvancedSearch) {
|
||||
return ADVANCED_SEARCH_TYPE;
|
||||
}
|
||||
|
||||
return !this.query.repository_ref || this.query.repository_ref === this.defaultBranchName;
|
||||
return BASIC_SEARCH_TYPE;
|
||||
},
|
||||
isEnabled() {
|
||||
const repoRef = this.query.repository_ref;
|
||||
switch (this.searchLevel) {
|
||||
case SEARCH_LEVEL_GLOBAL:
|
||||
case SEARCH_LEVEL_GROUP:
|
||||
return true;
|
||||
case SEARCH_LEVEL_PROJECT: {
|
||||
if (this.query.scope !== SCOPE_BLOB) {
|
||||
return true;
|
||||
}
|
||||
return !repoRef || repoRef === this.defaultBranchName;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
},
|
||||
isBasicSearch() {
|
||||
return this.searchType === BASIC_SEARCH_TYPE;
|
||||
|
|
@ -94,14 +120,14 @@ export default {
|
|||
<template>
|
||||
<div class="gl-text-gray-600">
|
||||
<div v-if="isBasicSearch" data-testid="basic"> </div>
|
||||
<div v-else-if="isEnabled" :data-testid="`${searchType}-enabled`">
|
||||
<div v-else-if="isEnabled" :data-testid="`${searchTypeTestId}-enabled`">
|
||||
<gl-sprintf :message="enabledMessage">
|
||||
<template #link="{ content }">
|
||||
<gl-link :href="helpUrl" target="_blank" data-testid="docs-link">{{ content }} </gl-link>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</div>
|
||||
<div v-else :data-testid="`${searchType}-disabled`">
|
||||
<div v-else :data-testid="`${searchTypeTestId}-disabled`">
|
||||
<gl-sprintf :message="disabledMessage">
|
||||
<template #link="{ content }">
|
||||
<gl-link :href="helpUrl" target="_blank" data-testid="docs-link">{{ content }} </gl-link>
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
position: relative;
|
||||
max-height: var(--read-more-height, #{$fallback - $height});
|
||||
scroll-padding-top: 1000vh; // Fix anchor scroll, keep it up top
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
// only appears when size is > $height.
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
module Mutations
|
||||
module SavedReplies
|
||||
class Base < BaseMutation
|
||||
field :saved_reply, Types::SavedReplyType,
|
||||
field :saved_reply, ::Types::Users::SavedReplyType,
|
||||
null: true,
|
||||
description: 'Saved reply after mutation.'
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ module Mutations
|
|||
|
||||
argument :id, Types::GlobalIDType[::Users::SavedReply],
|
||||
required: true,
|
||||
description: copy_field_description(Types::SavedReplyType, :id)
|
||||
description: copy_field_description(::Types::Users::SavedReplyType, :id)
|
||||
|
||||
def resolve(id:)
|
||||
saved_reply = authorized_find!(id: id)
|
||||
|
|
|
|||
|
|
@ -9,15 +9,15 @@ module Mutations
|
|||
|
||||
argument :id, Types::GlobalIDType[::Users::SavedReply],
|
||||
required: true,
|
||||
description: copy_field_description(Types::SavedReplyType, :id)
|
||||
description: copy_field_description(::Types::Users::SavedReplyType, :id)
|
||||
|
||||
argument :name, GraphQL::Types::String,
|
||||
required: true,
|
||||
description: copy_field_description(Types::SavedReplyType, :name)
|
||||
description: copy_field_description(::Types::SavedReplyType, :name)
|
||||
|
||||
argument :content, GraphQL::Types::String,
|
||||
required: true,
|
||||
description: copy_field_description(Types::SavedReplyType, :content)
|
||||
description: copy_field_description(::Types::SavedReplyType, :content)
|
||||
|
||||
def resolve(id:, name:, content:)
|
||||
saved_reply = authorized_find!(id: id)
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ module Resolvers
|
|||
type Types::CustomEmojiType, null: true
|
||||
|
||||
def resolve(**args)
|
||||
Groups::CustomEmojiFinder.new(object, args).execute
|
||||
::Groups::CustomEmojiFinder.new(object, args).execute
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
module Resolvers
|
||||
class SavedReplyResolver < BaseResolver
|
||||
type Types::SavedReplyType, null: true
|
||||
type ::Types::Users::SavedReplyType, null: true
|
||||
|
||||
alias_method :target, :object
|
||||
|
||||
|
|
@ -11,11 +11,7 @@ module Resolvers
|
|||
description: 'ID of a saved reply.'
|
||||
|
||||
def resolve(id:)
|
||||
saved_reply = ::Users::SavedReply.find_saved_reply(user_id: current_user.id, id: id.model_id)
|
||||
|
||||
return unless saved_reply
|
||||
|
||||
saved_reply
|
||||
::Users::SavedReply.find_saved_reply(user_id: current_user.id, id: id.model_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ module Resolvers
|
|||
private
|
||||
|
||||
def resolve_groups(**args)
|
||||
Groups::UserGroupsFinder.new(current_user, object, args).execute
|
||||
::Groups::UserGroupsFinder.new(current_user, object, args).execute
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,16 +2,10 @@
|
|||
|
||||
module Types
|
||||
class SavedReplyType < BaseObject
|
||||
graphql_name 'SavedReply'
|
||||
|
||||
connection_type_class Types::CountableConnectionType
|
||||
|
||||
authorize :read_saved_replies
|
||||
|
||||
field :id, Types::GlobalIDType[::Users::SavedReply],
|
||||
null: false,
|
||||
description: 'Global ID of the saved reply.'
|
||||
|
||||
field :content, GraphQL::Types::String,
|
||||
null: false,
|
||||
description: 'Content of the saved reply.'
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ module Types
|
|||
complexity: 5,
|
||||
resolver: ::Resolvers::TimelogResolver
|
||||
field :saved_replies,
|
||||
Types::SavedReplyType.connection_type,
|
||||
::Types::Users::SavedReplyType.connection_type,
|
||||
null: true,
|
||||
description: 'Saved replies authored by the user.'
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Types
|
||||
module Users
|
||||
class SavedReplyType < ::Types::SavedReplyType
|
||||
graphql_name 'SavedReply'
|
||||
|
||||
authorize :read_saved_replies
|
||||
|
||||
field :id, Types::GlobalIDType[::Users::SavedReply],
|
||||
null: false,
|
||||
description: 'Global ID of the user saved reply.'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -4,6 +4,7 @@ module Integrations
|
|||
class Teamcity < BaseCi
|
||||
include PushDataValidations
|
||||
include ReactivelyCached
|
||||
include HasAvatar
|
||||
prepend EnableSslVerification
|
||||
|
||||
TEAMCITY_SAAS_HOSTNAME = /\A[^\.]+\.teamcity\.com\z/i
|
||||
|
|
@ -91,6 +92,10 @@ module Integrations
|
|||
original_value.nil? ? (new_record? || url_is_saas?) : original_value
|
||||
end
|
||||
|
||||
def attribution_notice
|
||||
'Copyright © 2024 JetBrains s.r.o. JetBrains TeamCity and the JetBrains TeamCity logo are registered trademarks of JetBrains s.r.o.'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def url_is_saas?
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
module Integrations
|
||||
class Youtrack < BaseIssueTracker
|
||||
include Integrations::HasIssueTrackerFields
|
||||
include HasAvatar
|
||||
|
||||
validates :project_url, :issues_url, presence: true, public_url: true, if: :activated?
|
||||
|
||||
|
|
@ -15,16 +16,16 @@ module Integrations
|
|||
end
|
||||
|
||||
def self.title
|
||||
'YouTrack'
|
||||
'JetBrains YouTrack'
|
||||
end
|
||||
|
||||
def self.description
|
||||
s_("IssueTracker|Use YouTrack as this project's issue tracker.")
|
||||
s_("IssueTracker|Use JetBrains YouTrack as this project's issue tracker.")
|
||||
end
|
||||
|
||||
def self.help
|
||||
docs_link = ActionController::Base.helpers.link_to _('Learn more.'), Rails.application.routes.url_helpers.help_page_url('user/project/integrations/youtrack'), target: '_blank', rel: 'noopener noreferrer'
|
||||
s_("IssueTracker|Use YouTrack as this project's issue tracker. %{docs_link}").html_safe % { docs_link: docs_link.html_safe }
|
||||
s_("IssueTracker|Use JetBrains YouTrack as this project's issue tracker. %{docs_link}").html_safe % { docs_link: docs_link.html_safe }
|
||||
end
|
||||
|
||||
def self.to_param
|
||||
|
|
@ -34,5 +35,9 @@ module Integrations
|
|||
def self.fields
|
||||
super.select { %w[project_url issues_url].include?(_1.name) }
|
||||
end
|
||||
|
||||
def attribution_notice
|
||||
'Copyright © 2024 JetBrains s.r.o. JetBrains YouTrack and the JetBrains YouTrack logo are registered trademarks of JetBrains s.r.o.'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -652,7 +652,7 @@ class Note < ApplicationRecord
|
|||
end
|
||||
|
||||
def resource_parent
|
||||
project
|
||||
noteable.try(:resource_parent) || project
|
||||
end
|
||||
|
||||
def user_mentions
|
||||
|
|
@ -943,4 +943,4 @@ class Note < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
Note.prepend_mod_with('Note')
|
||||
Note.prepend_mod
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ module WorkItems
|
|||
original_description = description_param.fetch(:description, work_item.description)
|
||||
|
||||
description, command_params = QuickActions::InterpretService
|
||||
.new(work_item.project, current_user, {})
|
||||
.new(container: work_item.resource_parent, current_user: current_user)
|
||||
.execute(original_description, work_item)
|
||||
|
||||
description_param[:description] = description if description && description != original_description
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ module Groups
|
|||
def commands(noteable)
|
||||
return [] unless noteable
|
||||
|
||||
QuickActions::InterpretService.new(nil, current_user).available_commands(noteable)
|
||||
QuickActions::InterpretService.new(container: group, current_user: current_user).available_commands(noteable)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -210,9 +210,11 @@ class IssuableBaseService < ::BaseContainerService
|
|||
def merge_quick_actions_into_params!(issuable, only: nil)
|
||||
original_description = params.fetch(:description, issuable.description)
|
||||
|
||||
description, command_params =
|
||||
QuickActions::InterpretService.new(project, current_user, quick_action_options)
|
||||
.execute(original_description, issuable, only: only)
|
||||
description, command_params = QuickActions::InterpretService.new(
|
||||
container: container,
|
||||
current_user: current_user,
|
||||
params: quick_action_options
|
||||
).execute(original_description, issuable, only: only)
|
||||
|
||||
# Avoid a description already set on an issuable to be overwritten by a nil
|
||||
params[:description] = description if description && description != original_description
|
||||
|
|
|
|||
|
|
@ -34,7 +34,11 @@ module Notes
|
|||
def execute(note, options = {})
|
||||
return [note.note, {}] unless supported?(note)
|
||||
|
||||
@interpret_service = QuickActions::InterpretService.new(project, current_user, options)
|
||||
@interpret_service = QuickActions::InterpretService.new(
|
||||
container: note.resource_parent,
|
||||
current_user: current_user,
|
||||
params: options
|
||||
)
|
||||
|
||||
interpret_service.execute(note.note, note.noteable)
|
||||
end
|
||||
|
|
@ -90,4 +94,4 @@ module Notes
|
|||
end
|
||||
end
|
||||
|
||||
Notes::QuickActionsService.prepend_mod_with('Notes::QuickActionsService')
|
||||
Notes::QuickActionsService.prepend_mod
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ class PreviewMarkdownService < BaseContainerService
|
|||
def explain_quick_actions(text)
|
||||
return text, [] unless quick_action_types.include?(target_type)
|
||||
|
||||
quick_actions_service = QuickActions::InterpretService.new(project, current_user)
|
||||
quick_actions_service = QuickActions::InterpretService.new(container: container, current_user: current_user)
|
||||
quick_actions_service.explain(text, find_commands_target, keep_actions: params[:render_quick_actions])
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ module Projects
|
|||
def commands(noteable)
|
||||
return [] unless noteable && current_user
|
||||
|
||||
QuickActions::InterpretService.new(project, current_user).available_commands(noteable)
|
||||
QuickActions::InterpretService.new(container: project, current_user: current_user).available_commands(noteable)
|
||||
end
|
||||
|
||||
def snippets
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module QuickActions
|
||||
class InterpretService < BaseService
|
||||
class InterpretService < BaseContainerService
|
||||
include Gitlab::Utils::StrongMemoize
|
||||
include Gitlab::QuickActions::Dsl
|
||||
include Gitlab::QuickActions::IssueActions
|
||||
|
|
@ -109,12 +109,6 @@ module QuickActions
|
|||
project || group
|
||||
end
|
||||
|
||||
def group
|
||||
strong_memoize(:group) do
|
||||
quick_action_target.group if quick_action_target.respond_to?(:group)
|
||||
end
|
||||
end
|
||||
|
||||
def find_labels(labels_params = nil)
|
||||
extract_references(labels_params, :label) | find_labels_by_name_no_tilde(labels_params)
|
||||
end
|
||||
|
|
@ -218,4 +212,4 @@ module QuickActions
|
|||
end
|
||||
end
|
||||
|
||||
QuickActions::InterpretService.prepend_mod_with('QuickActions::InterpretService')
|
||||
QuickActions::InterpretService.prepend_mod
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
%aside.project-page-sidebar
|
||||
.project-page-sidebar-block.home-panel-home-desc.gl-py-4.gl-border-b.gl-border-gray-50{ class: 'gl-pt-2!' }
|
||||
.gl-display-flex.gl-justify-content-space-between
|
||||
%p.gl-font-weight-bold.gl-text-gray-900.gl-m-0.gl-mb-1= s_('ProjectPage|Project information')
|
||||
%h2.gl-font-base.gl-font-weight-bold.gl-reset-line-height.gl-text-gray-900.gl-m-0.gl-mb-1= s_('ProjectPage|Project information')
|
||||
-# Project settings
|
||||
- if can?(current_user, :admin_project, @project)
|
||||
= render Pajamas::ButtonComponent.new(href: edit_project_path(@project),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
.info-well
|
||||
.well-segment
|
||||
= markdown integration.help
|
||||
%p
|
||||
%em= integration.attribution_notice
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
.info-well
|
||||
.well-segment
|
||||
= markdown integration.help
|
||||
%p
|
||||
%em= integration.attribution_notice
|
||||
|
|
@ -260,7 +260,7 @@ module Gitlab
|
|||
# For more context, see: https://gitlab.com/gitlab-org/gitlab/-/issues/438278
|
||||
# Need to be loaded before initializers
|
||||
config.before_configuration do
|
||||
if Gitlab::Utils.to_boolean(ENV["USE_NEW_CSS_PIPELINE"])
|
||||
if Gitlab::Utils.to_boolean(ENV["USE_NEW_CSS_PIPELINE"], default: true)
|
||||
require 'cssbundling-rails'
|
||||
else
|
||||
require 'fileutils'
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
name: use_sync_service_token_worker
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/136078
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/431608
|
||||
milestone: '16.9'
|
||||
type: development
|
||||
group: group::cloud connector
|
||||
default_enabled: true
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddAncestorsColumnToSbomOccurrences < Gitlab::Database::Migration[2.2]
|
||||
milestone '16.10'
|
||||
|
||||
enable_lock_retries!
|
||||
|
||||
def up
|
||||
add_column :sbom_occurrences, :ancestors, :jsonb, default: [], null: false
|
||||
end
|
||||
|
||||
def down
|
||||
remove_column :sbom_occurrences, :ancestors
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ReplaceOldFkCiBuildTraceMetadataToCiJobArtifacts < Gitlab::Database::Migration[2.2]
|
||||
milestone '16.10'
|
||||
disable_ddl_transaction!
|
||||
|
||||
TABLE_NAME = :ci_build_trace_metadata
|
||||
REFERENCED_TABLE_NAME = :ci_job_artifacts
|
||||
FK_NAME = :fk_21d25cac1a_p
|
||||
TMP_FK_NAME = :tmp_fk_21d25cac1a_p
|
||||
|
||||
def up
|
||||
with_lock_retries do
|
||||
remove_foreign_key_if_exists(TABLE_NAME, REFERENCED_TABLE_NAME, name: FK_NAME, reverse_lock_order: true)
|
||||
rename_constraint(TABLE_NAME, TMP_FK_NAME, FK_NAME)
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
add_concurrent_foreign_key(TABLE_NAME,
|
||||
REFERENCED_TABLE_NAME,
|
||||
name: TMP_FK_NAME,
|
||||
column: [:partition_id, :trace_artifact_id],
|
||||
target_column: [:partition_id, :id],
|
||||
on_update: :cascade,
|
||||
on_delete: :cascade,
|
||||
validate: true,
|
||||
reverse_lock_order: true
|
||||
)
|
||||
|
||||
switch_constraint_names(TABLE_NAME, FK_NAME, TMP_FK_NAME)
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ReplaceOldFkCiJobArtifactStatesToCiJobArtifacts < Gitlab::Database::Migration[2.2]
|
||||
milestone '16.10'
|
||||
disable_ddl_transaction!
|
||||
|
||||
TABLE_NAME = :ci_job_artifact_states
|
||||
REFERENCED_TABLE_NAME = :ci_job_artifacts
|
||||
FK_NAME = :fk_rails_80a9cba3b2_p
|
||||
TMP_FK_NAME = :tmp_fk_rails_80a9cba3b2_p
|
||||
|
||||
def up
|
||||
with_lock_retries do
|
||||
remove_foreign_key_if_exists(TABLE_NAME, REFERENCED_TABLE_NAME, name: FK_NAME, reverse_lock_order: true)
|
||||
rename_constraint(TABLE_NAME, TMP_FK_NAME, FK_NAME)
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
add_concurrent_foreign_key(TABLE_NAME,
|
||||
REFERENCED_TABLE_NAME,
|
||||
name: TMP_FK_NAME,
|
||||
column: [:partition_id, :job_artifact_id],
|
||||
target_column: [:partition_id, :id],
|
||||
on_update: :cascade,
|
||||
on_delete: :cascade,
|
||||
validate: true,
|
||||
reverse_lock_order: true
|
||||
)
|
||||
|
||||
switch_constraint_names(TABLE_NAME, FK_NAME, TMP_FK_NAME)
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
83cb4871102a1096c688ef1f414c654581416479c1d082ba3540ae774d02ecfa
|
||||
|
|
@ -0,0 +1 @@
|
|||
ee6b6d16269e1d4c1addac4c7d403f196f7b339f0bb593677f874f652e14b4ed
|
||||
|
|
@ -0,0 +1 @@
|
|||
0c728d47b305f6ac23d902de40560fcb689ed293e249aa2e2d17de72ef9c57dd
|
||||
|
|
@ -15171,6 +15171,7 @@ CREATE TABLE sbom_occurrences (
|
|||
source_package_id bigint,
|
||||
archived boolean DEFAULT false NOT NULL,
|
||||
traversal_ids bigint[] DEFAULT '{}'::bigint[] NOT NULL,
|
||||
ancestors jsonb DEFAULT '[]'::jsonb NOT NULL,
|
||||
CONSTRAINT check_3f2d2c7ffc CHECK ((char_length(package_manager) <= 255)),
|
||||
CONSTRAINT check_9b29021fa8 CHECK ((char_length(component_name) <= 255)),
|
||||
CONSTRAINT check_e6b8437cfe CHECK ((char_length(input_file_path) <= 1024))
|
||||
|
|
@ -29327,7 +29328,7 @@ ALTER TABLE ONLY namespace_settings
|
|||
ADD CONSTRAINT fk_20cf0eb2f9 FOREIGN KEY (default_compliance_framework_id) REFERENCES compliance_management_frameworks(id) ON DELETE SET NULL;
|
||||
|
||||
ALTER TABLE ONLY ci_build_trace_metadata
|
||||
ADD CONSTRAINT fk_21d25cac1a_p FOREIGN KEY (partition_id, trace_artifact_id) REFERENCES ci_job_artifacts(partition_id, id) ON UPDATE CASCADE ON DELETE CASCADE;
|
||||
ADD CONSTRAINT fk_21d25cac1a_p FOREIGN KEY (partition_id, trace_artifact_id) REFERENCES p_ci_job_artifacts(partition_id, id) ON UPDATE CASCADE ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY users_star_projects
|
||||
ADD CONSTRAINT fk_22cd27ddfc FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
|
||||
|
|
@ -31295,7 +31296,7 @@ ALTER TABLE ONLY dependency_proxy_manifest_states
|
|||
ADD CONSTRAINT fk_rails_806cf07a3c FOREIGN KEY (dependency_proxy_manifest_id) REFERENCES dependency_proxy_manifests(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY ci_job_artifact_states
|
||||
ADD CONSTRAINT fk_rails_80a9cba3b2_p FOREIGN KEY (partition_id, job_artifact_id) REFERENCES ci_job_artifacts(partition_id, id) ON UPDATE CASCADE ON DELETE CASCADE;
|
||||
ADD CONSTRAINT fk_rails_80a9cba3b2_p FOREIGN KEY (partition_id, job_artifact_id) REFERENCES p_ci_job_artifacts(partition_id, id) ON UPDATE CASCADE ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY approval_merge_request_rules_users
|
||||
ADD CONSTRAINT fk_rails_80e6801803 FOREIGN KEY (approval_merge_request_rule_id) REFERENCES approval_merge_request_rules(id) ON DELETE CASCADE;
|
||||
|
|
@ -32230,12 +32231,6 @@ ALTER TABLE issue_search_data
|
|||
ALTER TABLE issue_search_data
|
||||
ADD CONSTRAINT issue_search_data_project_id_fkey FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY ci_build_trace_metadata
|
||||
ADD CONSTRAINT tmp_fk_21d25cac1a_p FOREIGN KEY (partition_id, trace_artifact_id) REFERENCES p_ci_job_artifacts(partition_id, id) ON UPDATE CASCADE ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY ci_job_artifact_states
|
||||
ADD CONSTRAINT tmp_fk_rails_80a9cba3b2_p FOREIGN KEY (partition_id, job_artifact_id) REFERENCES p_ci_job_artifacts(partition_id, id) ON UPDATE CASCADE ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY user_follow_users
|
||||
ADD CONSTRAINT user_follow_users_followee_id_fkey FOREIGN KEY (followee_id) REFERENCES users(id) ON DELETE CASCADE;
|
||||
|
||||
|
|
|
|||
|
|
@ -7043,7 +7043,7 @@ Input type: `SavedReplyDestroyInput`
|
|||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationsavedreplydestroyclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationsavedreplydestroyid"></a>`id` | [`UsersSavedReplyID!`](#userssavedreplyid) | Global ID of the saved reply. |
|
||||
| <a id="mutationsavedreplydestroyid"></a>`id` | [`UsersSavedReplyID!`](#userssavedreplyid) | Global ID of the user saved reply. |
|
||||
|
||||
#### Fields
|
||||
|
||||
|
|
@ -7063,7 +7063,7 @@ Input type: `SavedReplyUpdateInput`
|
|||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationsavedreplyupdateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationsavedreplyupdatecontent"></a>`content` | [`String!`](#string) | Content of the saved reply. |
|
||||
| <a id="mutationsavedreplyupdateid"></a>`id` | [`UsersSavedReplyID!`](#userssavedreplyid) | Global ID of the saved reply. |
|
||||
| <a id="mutationsavedreplyupdateid"></a>`id` | [`UsersSavedReplyID!`](#userssavedreplyid) | Global ID of the user saved reply. |
|
||||
| <a id="mutationsavedreplyupdatename"></a>`name` | [`String!`](#string) | Name of the saved reply. |
|
||||
|
||||
#### Fields
|
||||
|
|
@ -11389,6 +11389,30 @@ The edge type for [`GroupMember`](#groupmember).
|
|||
| <a id="groupmemberedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
|
||||
| <a id="groupmemberedgenode"></a>`node` | [`GroupMember`](#groupmember) | The item at the end of the edge. |
|
||||
|
||||
#### `GroupSavedReplyConnection`
|
||||
|
||||
The connection type for [`GroupSavedReply`](#groupsavedreply).
|
||||
|
||||
##### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="groupsavedreplyconnectioncount"></a>`count` | [`Int!`](#int) | Total count of collection. |
|
||||
| <a id="groupsavedreplyconnectionedges"></a>`edges` | [`[GroupSavedReplyEdge]`](#groupsavedreplyedge) | A list of edges. |
|
||||
| <a id="groupsavedreplyconnectionnodes"></a>`nodes` | [`[GroupSavedReply]`](#groupsavedreply) | A list of nodes. |
|
||||
| <a id="groupsavedreplyconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
|
||||
|
||||
#### `GroupSavedReplyEdge`
|
||||
|
||||
The edge type for [`GroupSavedReply`](#groupsavedreply).
|
||||
|
||||
##### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="groupsavedreplyedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
|
||||
| <a id="groupsavedreplyedgenode"></a>`node` | [`GroupSavedReply`](#groupsavedreply) | The item at the end of the edge. |
|
||||
|
||||
#### `GroupWikiRepositoryRegistryConnection`
|
||||
|
||||
The connection type for [`GroupWikiRepositoryRegistry`](#groupwikirepositoryregistry).
|
||||
|
|
@ -19925,6 +19949,7 @@ GPG signature for a signed commit.
|
|||
| <a id="grouprequestaccessenabled"></a>`requestAccessEnabled` | [`Boolean`](#boolean) | Indicates if users can request access to namespace. |
|
||||
| <a id="grouprequiretwofactorauthentication"></a>`requireTwoFactorAuthentication` | [`Boolean`](#boolean) | Indicates if all users in this group are required to set up two-factor authentication. |
|
||||
| <a id="grouprootstoragestatistics"></a>`rootStorageStatistics` | [`RootStorageStatistics`](#rootstoragestatistics) | Aggregated storage statistics of the namespace. Only available for root namespaces. |
|
||||
| <a id="groupsavedreplies"></a>`savedReplies` **{warning-solid}** | [`GroupSavedReplyConnection`](#groupsavedreplyconnection) | **Introduced** in 16.10. **Status**: Experiment. Saved replies available to the group. Available only when feature flag `group_saved_replies_flag` is enabled. This field can only be resolved for one group in any single request. |
|
||||
| <a id="groupsecuritypolicyproject"></a>`securityPolicyProject` | [`Project`](#project) | Security policy project assigned to the namespace. |
|
||||
| <a id="groupsharewithgrouplock"></a>`shareWithGroupLock` | [`Boolean`](#boolean) | Indicates if sharing a project with another group within this group is prevented. |
|
||||
| <a id="groupsharedrunnerssetting"></a>`sharedRunnersSetting` | [`SharedRunnersSetting`](#sharedrunnerssetting) | Shared runners availability for the namespace and its descendants. |
|
||||
|
|
@ -20807,6 +20832,22 @@ four standard [pagination arguments](#connection-pagination-arguments):
|
|||
| <a id="grouprunnersupgradestatus"></a>`upgradeStatus` | [`CiRunnerUpgradeStatus`](#cirunnerupgradestatus) | Filter by upgrade status. |
|
||||
| <a id="grouprunnersversionprefix"></a>`versionPrefix` **{warning-solid}** | [`String`](#string) | **Introduced** in 16.6. **Status**: Experiment. Filter runners by version. Runners that contain runner managers with the version at the start of the search term are returned. For example, the search term '14.' returns runner managers with versions '14.11.1' and '14.2.3'. |
|
||||
|
||||
##### `Group.savedReply`
|
||||
|
||||
Saved reply in the group. Available only when feature flag `group_saved_replies_flag` is enabled. This field can only be resolved for one group in any single request.
|
||||
|
||||
NOTE:
|
||||
**Introduced** in 16.10.
|
||||
**Status**: Experiment.
|
||||
|
||||
Returns [`GroupSavedReply`](#groupsavedreply).
|
||||
|
||||
###### Arguments
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="groupsavedreplyid"></a>`id` | [`GroupsSavedReplyID!`](#groupssavedreplyid) | Global ID of a saved reply. |
|
||||
|
||||
##### `Group.scanExecutionPolicies`
|
||||
|
||||
Scan Execution Policies of the namespace.
|
||||
|
|
@ -21118,6 +21159,16 @@ Contains release-related statistics about a group.
|
|||
| <a id="groupreleasestatsreleasescount"></a>`releasesCount` | [`Int`](#int) | Total number of releases in all descendant projects of the group. |
|
||||
| <a id="groupreleasestatsreleasespercentage"></a>`releasesPercentage` | [`Int`](#int) | Percentage of the group's descendant projects that have at least one release. |
|
||||
|
||||
### `GroupSavedReply`
|
||||
|
||||
#### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="groupsavedreplycontent"></a>`content` | [`String!`](#string) | Content of the saved reply. |
|
||||
| <a id="groupsavedreplyid"></a>`id` | [`GroupsSavedReplyID!`](#groupssavedreplyid) | Global ID of the group saved reply. |
|
||||
| <a id="groupsavedreplyname"></a>`name` | [`String!`](#string) | Name of the saved reply. |
|
||||
|
||||
### `GroupSecurityPolicySource`
|
||||
|
||||
Represents the source of a security policy belonging to a group.
|
||||
|
|
@ -27624,7 +27675,7 @@ Represents an entity for options in SAST CI configuration.
|
|||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="savedreplycontent"></a>`content` | [`String!`](#string) | Content of the saved reply. |
|
||||
| <a id="savedreplyid"></a>`id` | [`UsersSavedReplyID!`](#userssavedreplyid) | Global ID of the saved reply. |
|
||||
| <a id="savedreplyid"></a>`id` | [`UsersSavedReplyID!`](#userssavedreplyid) | Global ID of the user saved reply. |
|
||||
| <a id="savedreplyname"></a>`name` | [`String!`](#string) | Name of the saved reply. |
|
||||
|
||||
### `Scan`
|
||||
|
|
@ -33581,6 +33632,12 @@ A `GroupID` is a global ID. It is encoded as a string.
|
|||
|
||||
An example `GroupID` is: `"gid://gitlab/Group/1"`.
|
||||
|
||||
### `GroupsSavedReplyID`
|
||||
|
||||
A `GroupsSavedReplyID` is a global ID. It is encoded as a string.
|
||||
|
||||
An example `GroupsSavedReplyID` is: `"gid://gitlab/Groups::SavedReply/1"`.
|
||||
|
||||
### `ID`
|
||||
|
||||
Represents a unique identifier that is Base64 obfuscated. It is often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as `"VXNlci0xMA=="`) or integer (such as `4`) input value will be accepted as an ID.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,115 @@
|
|||
---
|
||||
stage: none
|
||||
group: Contributor Success
|
||||
info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/ee/development/development_processes.html#development-guidelines-review.
|
||||
---
|
||||
|
||||
# GitLab Beta program
|
||||
|
||||
The GitLab Beta program provides GitLab Beta program members with early access to exclusive features.
|
||||
This page lists features available for testing as part of the program.
|
||||
|
||||
WARNING:
|
||||
The GitLab Beta Program is not operational yet. This page is in draft & in preparation for an upcoming launch.
|
||||
|
||||
These features may not be ready for production use and follow the [Experimental or Beta policy](../../policy/experiment-beta-support.md) of GitLab.
|
||||
|
||||
## Git suggestions
|
||||
|
||||
- [GitLab CLI feature documentation](../../editor_extensions/gitlab_cli/index.md#gitlab-duo-commands)
|
||||
- [Feedback issue](https://gitlab.com/gitlab-org/gitlab/-/issues/409636)
|
||||
|
||||
<!-- Copied from ../../editor_extensions/gitlab_cli/index.md#gitlab-duo-commands -->
|
||||
Use `glab ask` to ask questions about `git` commands. It can help you remember a
|
||||
command you forgot, or provide suggestions on how to run commands to perform other tasks.
|
||||
|
||||
**Get started:**
|
||||
|
||||
1. To install GLab, see [installation instructions](https://gitlab.com/gitlab-org/cli/#installation).
|
||||
1. Set up [GitLab ClI Authentication](https://gitlab.com/gitlab-org/cli/#authentication).
|
||||
1. Use `glab ask git` to generate a Git command with AI in your command line:
|
||||
|
||||
```shell
|
||||
glab ask git <your_question>
|
||||
```
|
||||
|
||||
Examples:
|
||||
- `glab ask git how do I know the branch I'm on`
|
||||
- `glab ask git how to create a new branch with only a few particular commits`
|
||||
- `glab ask git how to find commits from removed branches`
|
||||
|
||||
After it replies, you can execute the command it generates.
|
||||
|
||||
## Code explanation
|
||||
|
||||
- [Code explanation feature documentation](../../user/ai_features.md#explain-code-in-the-web-ui-with-code-explanation)
|
||||
- [Feedback issue](https://gitlab.com/gitlab-org/gitlab/-/issues/407285)
|
||||
|
||||
<!-- from ../../user/ai_features.md#explain-code-in-the-web-ui-with-code-explanation -->
|
||||
With the help of a large language model, GitLab Duo can explain code in natural language.
|
||||
|
||||
**Get started:**
|
||||
|
||||
1. On the left sidebar, select **Search or go to** and find your project.
|
||||
1. Select any file in your project that contains code.
|
||||
1. On the file, select the lines that you want to have explained.
|
||||
1. On the left side, select the question mark (**{question}**). You might have to scroll to the
|
||||
first line of your selection to view it. This sends the selected code, together with a prompt,
|
||||
to provide an explanation to the large language model.
|
||||
1. A drawer is displayed on the right side of the page. Wait a moment for the explanation to be generated.
|
||||
|
||||
## GitLab Duo Chat
|
||||
|
||||
- [GitLab Duo Chat feature documentation](../../user/gitlab_duo_chat.md)
|
||||
- [Feedback issue](https://gitlab.com/gitlab-org/gitlab/-/issues/430124)
|
||||
|
||||
<!-- from ../../user/gitlab_duo_chat.md -->
|
||||
GitLab Duo Chat is your personal AI-powered assistant for boosting productivity.
|
||||
It can assist various tasks of your daily work with the AI-generated content.
|
||||
|
||||
**Get started:**
|
||||
|
||||
1. On the left sidebar, select **Help** (**{question-o}**) > **GitLab Duo Chat**.
|
||||
1. GitLab Duo Chat opens in the right sidebar. Enter your question or try one of these examples:
|
||||
- `Where to find docs for CI job artifacts configuration?`
|
||||
- `Explain the concept of a 'fork' in a concise manner.`
|
||||
- `Provide step-by-step instructions on how to reset a user's password.`
|
||||
|
||||
## Generate issue description
|
||||
|
||||
- [Generate issue description feature documentation](../../user/ai_features.md#summarize-an-issue-with-issue-description-generation)
|
||||
- [Feedback issue](https://gitlab.com/gitlab-org/gitlab/-/issues/409844)
|
||||
|
||||
<!-- from ../../user/ai_features.md#summarize-an-issue-with-issue-description-generation -->
|
||||
Write a short summary of an issue and GitLab Duo generates a description for you.
|
||||
|
||||
**Get started:**
|
||||
|
||||
1. Create a new issue.
|
||||
1. Above the **Description** field, select **AI actions > Generate issue description**. Hint: AI actions can be found next to **Preview**
|
||||
1. Write a short description and select **Submit**.
|
||||
|
||||
GitLab Duo replaces the issue description with AI-generated text.
|
||||
|
||||
## Test generation
|
||||
|
||||
- [Tests generation feature documentation](../../user/gitlab_duo_chat.md#write-tests-in-the-ide)
|
||||
- [Feedback issue](https://gitlab.com/gitlab-org/gitlab/-/issues/430124)
|
||||
|
||||
<!-- ../../user/gitlab_duo_chat.md#write-tests-in-the-ide -->
|
||||
`/tests` is a special command to generate a testing suggestion for the selected code in your editor.
|
||||
This feature is available in VS Code and the Web IDE only.
|
||||
|
||||
**Get started:**
|
||||
|
||||
1. On the left sidebar, select **Search or go to** and find your project.
|
||||
1. Go to your file or directory.
|
||||
1. Select **Edit > Open in Web IDE**.
|
||||
1. Select code inside file.
|
||||
1. On the left sidebar of Web IDE select the **GitLab Duo Chat** icon.
|
||||
1. Enter `/tests` in AI dialog.
|
||||
|
||||
You can add additional instructions:
|
||||
- `/tests using RSpec framework`
|
||||
- `/tests markdown syntax`
|
||||
1. GitLab Duo Chat returns a code block with an example RSpec test you can use for the code you selected.
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
---
|
||||
stage: none
|
||||
group: Contributor Success
|
||||
info: Any user with at least the Maintainer role can merge updates to this content. For details, see https://docs.gitlab.com/ee/development/development_processes.html#development-guidelines-review.
|
||||
---
|
||||
|
||||
# GitLab Beta Program status
|
||||
|
||||
NOTE:
|
||||
Last status update 2024-02-20.
|
||||
|
||||
These features may not be ready for production use and follow the [Experimental or Beta policy](../../policy/experiment-beta-support.md) of GitLab
|
||||
|
||||
WARNING:
|
||||
The GitLab Beta Program is not operational yet. This page is in draft & in preparation for an upcoming launch.
|
||||
|
||||
## Features currently enrolled in the GitLab Beta Program
|
||||
|
||||
| Name | Status | Enrolled at |
|
||||
|-----------------------------------------------------------------------------------|------------|---------------|
|
||||
| [GitLab Duo - Git suggestions](features.md#git-suggestions) | [Experiment](../../policy/experiment-beta-support.md#experiment) | 2024-03-01 |
|
||||
| [GitLab Duo - Code explanation](features.md#code-explanation) | [Experiment](../../policy/experiment-beta-support.md#experiment) | 2024-03-01 |
|
||||
| [GitLab Duo - Chat](features.md#gitlab-duo-chat) | [Beta](../../policy/experiment-beta-support.md#beta) | 2024-03-01 |
|
||||
| [GitLab Duo - Generate issue description](features.md#generate-issue-description) | [Experiment](../../policy/experiment-beta-support.md#experiment) | 2024-03-01 |
|
||||
| [GitLab Duo - Test generation](features.md#test-generation) | [Beta](../../policy/experiment-beta-support.md#beta) | 2024-03-01 |
|
||||
|
||||
<!--
|
||||
## Features previously enrolled
|
||||
|
||||
| Name | Status | Enrolled at | Removed at |
|
||||
|-----------------------------------------------------------------------------------|------------|---------------| -------------|
|
||||
| | | | |
|
||||
-->
|
||||
|
|
@ -158,7 +158,7 @@ module Keeps
|
|||
def each_feature_flag
|
||||
all_feature_flag_files.map do |f|
|
||||
yield(
|
||||
Feature::Definition.new(f, YAML.load_file(f, permitted_classes: [Symbol], symbolize_names: true))
|
||||
Feature::Definition.new(f, YAML.safe_load_file(f, permitted_classes: [Symbol], symbolize_names: true))
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ module Gitlab
|
|||
|
||||
def parse
|
||||
::Gitlab::Ci::Reports::Sbom::Component.new(
|
||||
ref: data['bom-ref'],
|
||||
type: data['type'],
|
||||
name: data['name'],
|
||||
purl: purl,
|
||||
|
|
|
|||
|
|
@ -39,6 +39,8 @@ module Gitlab
|
|||
def parse_report
|
||||
parse_metadata_properties
|
||||
parse_components
|
||||
parse_dependencies
|
||||
report.ensure_ancestors!
|
||||
end
|
||||
|
||||
def parse_metadata_properties
|
||||
|
|
@ -64,6 +66,15 @@ module Gitlab
|
|||
report.add_error("/components/#{index}/purl is invalid")
|
||||
end
|
||||
end
|
||||
|
||||
def parse_dependencies
|
||||
data['dependencies']&.each do |dependency_data|
|
||||
ref = dependency_data['ref']
|
||||
dependency_data['dependsOn']&.each do |dependency_ref|
|
||||
report.add_dependency(ref, dependency_ref)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -7,16 +7,18 @@ module Gitlab
|
|||
class Component
|
||||
include Gitlab::Utils::StrongMemoize
|
||||
|
||||
attr_reader :component_type, :version, :path
|
||||
attr_accessor :properties, :purl, :source_package_name
|
||||
attr_reader :ref, :component_type, :version, :path
|
||||
attr_accessor :properties, :purl, :source_package_name, :ancestors
|
||||
|
||||
def initialize(type:, name:, purl:, version:, properties: nil, source_package_name: nil)
|
||||
def initialize(ref:, type:, name:, purl:, version:, properties: nil, source_package_name: nil)
|
||||
@ref = ref
|
||||
@component_type = type
|
||||
@name = name
|
||||
@purl = purl
|
||||
@version = version
|
||||
@properties = properties
|
||||
@source_package_name = source_package_name
|
||||
@ancestors = []
|
||||
end
|
||||
|
||||
def <=>(other)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Ci
|
||||
module Reports
|
||||
module Sbom
|
||||
class DependencyAdjacencyList
|
||||
def initialize
|
||||
@adjacency_list = Hash.new { |hash, key| hash[key] = [] }
|
||||
@component_info = {}
|
||||
end
|
||||
|
||||
def add_edge(parent, child)
|
||||
adjacency_list[child] << parent
|
||||
end
|
||||
|
||||
def add_component_info(ref, name, version)
|
||||
component_info[ref] = { name: name, version: version }
|
||||
end
|
||||
|
||||
def ancestors_for(child)
|
||||
ancestors_ref_for(child).filter_map do |ancestor_ref|
|
||||
component_info[ancestor_ref]
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def ancestors_ref_for(child)
|
||||
adjacency_list[child]
|
||||
end
|
||||
|
||||
attr_reader :adjacency_list, :component_info
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -23,6 +23,7 @@ module Gitlab
|
|||
}
|
||||
@components = []
|
||||
@metadata = ::Gitlab::Ci::Reports::Sbom::Metadata.new
|
||||
@dependencies = DependencyAdjacencyList.new
|
||||
@errors = []
|
||||
end
|
||||
|
||||
|
|
@ -40,11 +41,27 @@ module Gitlab
|
|||
|
||||
def add_component(component)
|
||||
components << component
|
||||
dependencies.add_component_info(component.ref, component.name, component.version)
|
||||
end
|
||||
|
||||
def add_dependency(parent, child)
|
||||
dependencies.add_edge(parent, child)
|
||||
end
|
||||
|
||||
def ensure_ancestors!
|
||||
components.each do |component|
|
||||
component.ancestors = ancestors_for(component.ref)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def ancestors_for(ref)
|
||||
dependencies.ancestors_for(ref)
|
||||
end
|
||||
|
||||
attr_writer :source
|
||||
attr_reader :dependencies
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -110,9 +110,7 @@ module Search
|
|||
end
|
||||
|
||||
def show_wiki_search_tab?
|
||||
return true if tab_enabled_for_project?(:wiki_blobs)
|
||||
|
||||
project.nil? && show_elasticsearch_tabs? && feature_flag_tab_enabled?(:global_search_wiki_tab)
|
||||
tab_enabled_for_project?(:wiki_blobs)
|
||||
end
|
||||
|
||||
def show_commits_search_tab?
|
||||
|
|
@ -147,6 +145,7 @@ module Search
|
|||
project.nil? || tab_enabled_for_project?(:milestones)
|
||||
end
|
||||
|
||||
# deprecated - this method is being refactored and will eventually be removed
|
||||
def feature_flag_tab_enabled?(flag)
|
||||
group.present? || Feature.enabled?(flag, user, type: :ops)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ module Tasks
|
|||
puts "Generating the SHA256 hash for #{asset_files.size} Webpack-related assets..." if verbose
|
||||
|
||||
assets_sha256 = asset_files.map { |asset_file| Digest::SHA256.file(asset_file).hexdigest }.join
|
||||
assets_sha256 += ENV.fetch('USE_NEW_CSS_PIPELINE', 'true')
|
||||
|
||||
Digest::SHA256.hexdigest(assets_sha256).tap { |sha256| puts "=> SHA256 generated in #{Time.now - start_time}: #{sha256}" if verbose }
|
||||
end
|
||||
|
|
|
|||
|
|
@ -27246,18 +27246,18 @@ msgstr ""
|
|||
msgid "IssueTracker|Use IBM Engineering Workflow Management as this project's issue tracker. %{docs_link}"
|
||||
msgstr ""
|
||||
|
||||
msgid "IssueTracker|Use JetBrains YouTrack as this project's issue tracker."
|
||||
msgstr ""
|
||||
|
||||
msgid "IssueTracker|Use JetBrains YouTrack as this project's issue tracker. %{docs_link}"
|
||||
msgstr ""
|
||||
|
||||
msgid "IssueTracker|Use Redmine as the issue tracker. %{docs_link}"
|
||||
msgstr ""
|
||||
|
||||
msgid "IssueTracker|Use Redmine as this project's issue tracker."
|
||||
msgstr ""
|
||||
|
||||
msgid "IssueTracker|Use YouTrack as this project's issue tracker."
|
||||
msgstr ""
|
||||
|
||||
msgid "IssueTracker|Use YouTrack as this project's issue tracker. %{docs_link}"
|
||||
msgstr ""
|
||||
|
||||
msgid "IssueTracker|Use a custom issue tracker as this project's issue tracker."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -32788,9 +32788,6 @@ msgstr ""
|
|||
msgid "No credit card data for matching"
|
||||
msgstr ""
|
||||
|
||||
msgid "No credit card required."
|
||||
msgstr ""
|
||||
|
||||
msgid "No data"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -52785,7 +52782,7 @@ msgstr ""
|
|||
msgid "Try again?"
|
||||
msgstr ""
|
||||
|
||||
msgid "Try all GitLab has to offer for 30 days."
|
||||
msgid "Try all GitLab has to offer for 30 days. No credit card required."
|
||||
msgstr ""
|
||||
|
||||
msgid "Try changing or removing filters."
|
||||
|
|
|
|||
|
|
@ -172,6 +172,11 @@ export async function compileAllStyles({ shouldWatch = false }) {
|
|||
const sassCompilerOptions = {
|
||||
loadPaths: resolveLoadPaths(),
|
||||
logger: Logger.silent,
|
||||
// For now we compress CSS directly with SASS if we do not watch
|
||||
// We probably want to change this later if there are more
|
||||
// post-processing steps, because we would compress
|
||||
// _after_ things like auto-prefixer, etc. happened
|
||||
style: shouldWatch ? 'expanded' : 'compressed',
|
||||
};
|
||||
|
||||
let fileWatcher = null;
|
||||
|
|
@ -218,8 +223,20 @@ export async function compileAllStyles({ shouldWatch = false }) {
|
|||
return fileWatcher;
|
||||
}
|
||||
|
||||
function shouldUseNewPipeline() {
|
||||
return /^(true|t|yes|y|1|on)$/i.test(`${env.USE_NEW_CSS_PIPELINE}`);
|
||||
// Mirroring gems/gitlab-utils/lib/gitlab/utils.rb
|
||||
function shouldUseNewPipeline(defaultValue = true) {
|
||||
if ([true, false, 0, 1].includes(env?.USE_NEW_CSS_PIPELINE)) {
|
||||
return env.USE_NEW_CSS_PIPELINE;
|
||||
}
|
||||
|
||||
if (/^(true|t|yes|y|1|on)$/i.test(`${env.USE_NEW_CSS_PIPELINE}`)) {
|
||||
return true;
|
||||
}
|
||||
if (/^(false|f|no|n|0|off)$/i.test(`${env.USE_NEW_CSS_PIPELINE}`)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
export function viteCSSCompilerPlugin({ shouldWatch = true }) {
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ variables:
|
|||
QA_SUITES: "$QA_SUITES"
|
||||
QA_TESTS: "$QA_TESTS"
|
||||
KNAPSACK_TEST_FILE_PATTERN: "$KNAPSACK_TEST_FILE_PATTERN"
|
||||
USE_NEW_CSS_PIPELINE: "true"
|
||||
YML
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ FactoryBot.define do
|
|||
factory :ci_reports_sbom_component, class: '::Gitlab::Ci::Reports::Sbom::Component' do
|
||||
type { "library" }
|
||||
|
||||
sequence(:ref) { |n| "ref-#{n}" }
|
||||
sequence(:name) { |n| "component-#{n}" }
|
||||
sequence(:version) { |n| "v0.0.#{n}" }
|
||||
|
||||
|
|
@ -42,6 +43,7 @@ FactoryBot.define do
|
|||
|
||||
initialize_with do
|
||||
::Gitlab::Ci::Reports::Sbom::Component.new(
|
||||
ref: ref,
|
||||
type: type,
|
||||
name: name,
|
||||
purl: purl,
|
||||
|
|
|
|||
|
|
@ -65,10 +65,8 @@ RSpec.describe 'Group Package and registry settings', feature_category: :package
|
|||
|
||||
wait_for_requests
|
||||
|
||||
# rubocop:disable Capybara/TestidFinders -- Helper within_testid doesn't cover use case
|
||||
expect(page).to be_axe_clean.within('[data-testid="packages-and-registries-group-settings"]')
|
||||
expect(page).to be_axe_clean.within('[data-testid="packages-and-registries-group-settings"]') # rubocop:todo Capybara/TestidFinders -- Doesn't cover use case, see https://gitlab.com/gitlab-org/gitlab/-/issues/442224
|
||||
.skipping :'link-in-text-block', :'heading-order'
|
||||
# rubocop:enable Capybara/TestidFinders
|
||||
end
|
||||
|
||||
it 'has a Duplicate packages section', :js do
|
||||
|
|
|
|||
|
|
@ -110,12 +110,10 @@ RSpec.describe 'Discussion Lock', :js, feature_category: :team_planning do
|
|||
visit project_issue_path(project, issue)
|
||||
wait_for_all_requests
|
||||
|
||||
# rubocop:disable Capybara/TestidFinders -- within_testid does not work here
|
||||
expect(page).to be_axe_clean.within(locked_badge)
|
||||
expect(page).to be_axe_clean.within(issuable_note_warning)
|
||||
|
||||
more_dropdown.click
|
||||
expect(page).to be_axe_clean.within('[data-testid="lock-issue-toggle"] button')
|
||||
# rubocop:enable Capybara/TestidFinders
|
||||
expect(page).to be_axe_clean.within('[data-testid="lock-issue-toggle"] button') # rubocop:todo Capybara/TestidFinders -- Doesn't cover use case, see https://gitlab.com/gitlab-org/gitlab/-/issues/442224
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -221,34 +221,28 @@ RSpec.describe 'Navigation menu item pinning', :js, feature_category: :navigatio
|
|||
nav_item = find_by_testid('nav-item', text: nav_item_title)
|
||||
scroll_to(nav_item)
|
||||
nav_item.hover
|
||||
within(nav_item) do
|
||||
pin_button = find_by_testid('nav-item-pin')
|
||||
pin_button.click
|
||||
wait_for_requests
|
||||
end
|
||||
find_by_testid('nav-item-pin', context: nav_item).click
|
||||
|
||||
wait_for_requests
|
||||
end
|
||||
|
||||
def remove_pin(nav_item_title)
|
||||
nav_item = find_by_testid('nav-item', text: nav_item_title)
|
||||
scroll_to(nav_item)
|
||||
nav_item.hover
|
||||
within(nav_item) do
|
||||
unpin_button = find_by_testid('nav-item-unpin')
|
||||
unpin_button.click
|
||||
wait_for_requests
|
||||
end
|
||||
find_by_testid('nav-item-unpin', context: nav_item).click
|
||||
|
||||
wait_for_requests
|
||||
end
|
||||
|
||||
def drag_item(item, to:)
|
||||
item.hover
|
||||
within(item) do
|
||||
drag_handle = find_by_testid('grip-icon')
|
||||
|
||||
# Reduce delay to make it less likely for draggables to
|
||||
# change position during drag operation, which reduces
|
||||
# flakiness.
|
||||
drag_handle.drag_to(to, delay: 0.01)
|
||||
wait_for_requests
|
||||
end
|
||||
# Reduce delay to make it less likely for draggables to
|
||||
# change position during drag operation, which reduces
|
||||
# flakiness.
|
||||
find_by_testid('grip-icon', context: item).drag_to(to, delay: 0.01)
|
||||
|
||||
wait_for_requests
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,14 +1,6 @@
|
|||
import { DEFAULT_TH_CLASSES } from '~/lib/utils/constants';
|
||||
import * as tableUtils from '~/lib/utils/table_utility';
|
||||
|
||||
describe('table_utility', () => {
|
||||
describe('thWidthClass', () => {
|
||||
it('returns the width class including default table header classes', () => {
|
||||
const width = 50;
|
||||
expect(tableUtils.thWidthClass(width)).toBe(`gl-w-${width}p ${DEFAULT_TH_CLASSES}`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('thWidthPercent', () => {
|
||||
it('returns the width class including default table header classes', () => {
|
||||
const width = 50;
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ describe('AssociationCountCard', () => {
|
|||
const defaultPropsData = {
|
||||
title: 'Groups',
|
||||
iconName: 'group',
|
||||
count: 1050,
|
||||
count: '1000+',
|
||||
linkHref: '/-/organizations/default/groups_and_projects?display=groups',
|
||||
};
|
||||
|
||||
|
|
@ -28,7 +28,7 @@ describe('AssociationCountCard', () => {
|
|||
const link = findLink();
|
||||
|
||||
expect(card.text()).toContain(defaultPropsData.title);
|
||||
expect(card.text()).toContain('1k');
|
||||
expect(card.text()).toContain('1000+');
|
||||
expect(link.text()).toBe('View all');
|
||||
expect(link.attributes('href')).toBe(defaultPropsData.linkHref);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@ describe('AssociationCounts', () => {
|
|||
|
||||
const defaultPropsData = {
|
||||
associationCounts: {
|
||||
groups: 10,
|
||||
projects: 5,
|
||||
users: 6,
|
||||
groups: '10',
|
||||
projects: '5',
|
||||
users: '1000+',
|
||||
},
|
||||
groupsAndProjectsOrganizationPath: '/-/organizations/default/groups_and_projects',
|
||||
usersOrganizationPath: '/-/organizations/default/users',
|
||||
|
|
|
|||
|
|
@ -42,20 +42,27 @@ describe('SearchTypeIndicator', () => {
|
|||
// all possible combinations
|
||||
|
||||
describe.each`
|
||||
searchType | searchLevel | repository | showSearchTypeIndicator
|
||||
${'advanced'} | ${'project'} | ${'master'} | ${'advanced-enabled'}
|
||||
${'advanced'} | ${'project'} | ${'v0.1'} | ${'advanced-disabled'}
|
||||
${'advanced'} | ${'group'} | ${'master'} | ${'advanced-enabled'}
|
||||
${'advanced'} | ${'global'} | ${'master'} | ${'advanced-enabled'}
|
||||
${'zoekt'} | ${'project'} | ${'master'} | ${'zoekt-enabled'}
|
||||
${'zoekt'} | ${'project'} | ${'v0.1'} | ${'zoekt-disabled'}
|
||||
${'zoekt'} | ${'group'} | ${'master'} | ${'zoekt-enabled'}
|
||||
searchType | searchLevel | repository | scope | showSearchTypeIndicator
|
||||
${'advanced'} | ${'project'} | ${'master'} | ${'blobs'} | ${'advanced-enabled'}
|
||||
${'advanced'} | ${'project'} | ${'v0.1'} | ${'blobs'} | ${'advanced-disabled'}
|
||||
${'advanced'} | ${'group'} | ${'master'} | ${'blobs'} | ${'advanced-enabled'}
|
||||
${'advanced'} | ${'global'} | ${'master'} | ${'blobs'} | ${'advanced-enabled'}
|
||||
${'zoekt'} | ${'project'} | ${'master'} | ${'blobs'} | ${'zoekt-enabled'}
|
||||
${'zoekt'} | ${'project'} | ${'v0.1'} | ${'blobs'} | ${'zoekt-disabled'}
|
||||
${'zoekt'} | ${'group'} | ${'master'} | ${'blobs'} | ${'zoekt-enabled'}
|
||||
${'advanced'} | ${'project'} | ${'master'} | ${'issues'} | ${'advanced-enabled'}
|
||||
${'advanced'} | ${'project'} | ${'v0.1'} | ${'issues'} | ${'advanced-enabled'}
|
||||
${'advanced'} | ${'group'} | ${'master'} | ${'issues'} | ${'advanced-enabled'}
|
||||
${'advanced'} | ${'global'} | ${'master'} | ${'issues'} | ${'advanced-enabled'}
|
||||
${'zoekt'} | ${'project'} | ${'master'} | ${'issues'} | ${'advanced-enabled'}
|
||||
${'zoekt'} | ${'project'} | ${'v0.1'} | ${'issues'} | ${'advanced-enabled'}
|
||||
${'zoekt'} | ${'group'} | ${'master'} | ${'issues'} | ${'advanced-enabled'}
|
||||
`(
|
||||
'search type indicator for $searchType $searchLevel',
|
||||
({ searchType, repository, showSearchTypeIndicator, searchLevel }) => {
|
||||
'search type indicator for $searchType $searchLevel $scope',
|
||||
({ searchType, repository, showSearchTypeIndicator, scope, searchLevel }) => {
|
||||
beforeEach(() => {
|
||||
createComponent({
|
||||
query: { repository_ref: repository },
|
||||
query: { repository_ref: repository, scope },
|
||||
searchType,
|
||||
searchLevel,
|
||||
defaultBranchName: 'master',
|
||||
|
|
@ -76,8 +83,9 @@ describe('SearchTypeIndicator', () => {
|
|||
({ searchType, repository, showSearchTypeIndicator }) => {
|
||||
beforeEach(() => {
|
||||
createComponent({
|
||||
query: { repository_ref: repository },
|
||||
query: { repository_ref: repository, scope: 'blobs' },
|
||||
searchType,
|
||||
searchLevel: 'project',
|
||||
defaultBranchName: 'master',
|
||||
});
|
||||
});
|
||||
|
|
@ -97,7 +105,7 @@ describe('SearchTypeIndicator', () => {
|
|||
`('documentation link for $searchType', ({ searchType, docsLink }) => {
|
||||
beforeEach(() => {
|
||||
createComponent({
|
||||
query: { repository_ref: 'master' },
|
||||
query: { repository_ref: 'master', scope: 'blobs' },
|
||||
searchType,
|
||||
searchLevel: 'project',
|
||||
defaultBranchName: 'master',
|
||||
|
|
@ -115,7 +123,7 @@ describe('SearchTypeIndicator', () => {
|
|||
`('Syntax documentation $searchType', ({ searchType, syntaxdocsLink }) => {
|
||||
beforeEach(() => {
|
||||
createComponent({
|
||||
query: { repository_ref: '000' },
|
||||
query: { repository_ref: '000', scope: 'blobs' },
|
||||
searchType,
|
||||
searchLevel: 'project',
|
||||
defaultBranchName: 'master',
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ RSpec.describe Gitlab::Ci::Parsers::Sbom::Component, feature_category: :dependen
|
|||
expect(component.purl.name).to eq("activesupport")
|
||||
expect(component.properties).to be_nil
|
||||
expect(component.source_package_name).to be_nil
|
||||
expect(component.ref).to eq("pkg:gem/activesupport@5.1.4")
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -327,4 +327,22 @@ RSpec.describe Gitlab::Ci::Parsers::Sbom::Cyclonedx, feature_category: :dependen
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when cyclonedx report has no dependencies' do
|
||||
it 'skips component processing' do
|
||||
expect(report).not_to receive(:add_dependency)
|
||||
|
||||
parse!
|
||||
end
|
||||
end
|
||||
|
||||
context 'when report has dependencies' do
|
||||
let(:report_data) { base_report_data.merge({ 'dependencies' => [{ ref: 'ref', dependsOn: ['dependency_ref'] }] }) }
|
||||
|
||||
it 'passes dependencies to report' do
|
||||
expect(report).to receive(:add_dependency).with('ref', 'dependency_ref')
|
||||
|
||||
parse!
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -9,9 +9,11 @@ RSpec.describe Gitlab::Ci::Reports::Sbom::Component, feature_category: :dependen
|
|||
let(:purl) { Sbom::PackageUrl.new(type: purl_type, name: name, version: version) }
|
||||
let(:version) { 'v0.0.1' }
|
||||
let(:source_package_name) { 'source-component' }
|
||||
let(:ref) { 'ref' }
|
||||
|
||||
subject(:component) do
|
||||
described_class.new(
|
||||
ref: ref,
|
||||
type: component_type,
|
||||
name: name,
|
||||
purl: purl,
|
||||
|
|
@ -168,6 +170,7 @@ RSpec.describe Gitlab::Ci::Reports::Sbom::Component, feature_category: :dependen
|
|||
with_them do
|
||||
specify do
|
||||
a = described_class.new(
|
||||
ref: ref,
|
||||
name: a_name,
|
||||
type: a_type,
|
||||
purl: a_purl,
|
||||
|
|
@ -175,6 +178,7 @@ RSpec.describe Gitlab::Ci::Reports::Sbom::Component, feature_category: :dependen
|
|||
)
|
||||
|
||||
b = described_class.new(
|
||||
ref: ref,
|
||||
name: b_name,
|
||||
type: b_type,
|
||||
purl: b_purl,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::Ci::Reports::Sbom::DependencyAdjacencyList, feature_category: :dependency_management do
|
||||
subject(:dependency_list) { described_class.new }
|
||||
|
||||
let(:child) { 'child-ref' }
|
||||
|
||||
describe 'without any data' do
|
||||
it 'does not return any ancestor' do
|
||||
expect(dependency_list.ancestors_for(child)).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
describe 'with only relationship data' do
|
||||
let(:parent) { 'parent-ref' }
|
||||
|
||||
before do
|
||||
dependency_list.add_edge(parent, child)
|
||||
end
|
||||
|
||||
it 'does not return any ancestor' do
|
||||
expect(dependency_list.ancestors_for(child)).to be_empty
|
||||
end
|
||||
|
||||
context 'with component data' do
|
||||
let(:component_data) { { name: 'component_name', version: 'component_version' } }
|
||||
|
||||
before do
|
||||
dependency_list.add_component_info(parent, component_data[:name], component_data[:version])
|
||||
end
|
||||
|
||||
it 'returns the ancestor' do
|
||||
expect(dependency_list.ancestors_for(child)).to eq([component_data])
|
||||
end
|
||||
|
||||
context 'with multiple ancestors' do
|
||||
let(:component_data_2) { { name: 'component_name_2', version: 'component_version_2' } }
|
||||
let(:parent_2) { 'parent_2-ref' }
|
||||
|
||||
before do
|
||||
dependency_list.add_component_info(parent_2, component_data_2[:name], component_data_2[:version])
|
||||
dependency_list.add_edge(parent_2, child)
|
||||
end
|
||||
|
||||
it 'returns the ancestor' do
|
||||
expect(dependency_list.ancestors_for(child)).to contain_exactly(component_data, component_data_2)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -48,4 +48,20 @@ RSpec.describe Gitlab::Ci::Reports::Sbom::Report, feature_category: :dependency_
|
|||
expect(report.components).to match_array(components)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'ensure_ancestors!' do
|
||||
let_it_be(:components) { create_list(:ci_reports_sbom_component, 3) }
|
||||
let_it_be(:component_first) { components.first }
|
||||
let_it_be(:component_last) { components.last }
|
||||
let_it_be(:expected_value) { { name: component_first.name, version: component_first.version } }
|
||||
|
||||
it 'stores hierachies' do
|
||||
components.each { |component| report.add_component(component) }
|
||||
report.add_dependency(component_first.ref, component_last.ref)
|
||||
|
||||
report.ensure_ancestors!
|
||||
|
||||
expect(component_last.ancestors).to match_array([expected_value])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -141,23 +141,19 @@ RSpec.describe Search::Navigation, feature_category: :global_search do
|
|||
end
|
||||
|
||||
context 'for wiki tab' do
|
||||
where(:feature_flag_enabled, :show_elasticsearch_tabs, :project, :tab_enabled, :condition) do
|
||||
false | false | nil | true | true
|
||||
false | false | nil | false | false
|
||||
false | false | ref(:project_double) | false | false
|
||||
false | true | nil | false | false
|
||||
false | true | ref(:project_double) | false | false
|
||||
true | false | nil | false | false
|
||||
true | true | ref(:project_double) | false | false
|
||||
where(:project, :group, :tab_enabled_for_project, :condition) do
|
||||
nil | nil | false | false
|
||||
nil | ref(:group_double) | false | false
|
||||
ref(:project_double) | nil | true | true
|
||||
ref(:project_double) | nil | false | false
|
||||
end
|
||||
|
||||
with_them do
|
||||
let(:options) { { show_elasticsearch_tabs: show_elasticsearch_tabs } }
|
||||
let(:options) { {} }
|
||||
|
||||
it 'data item condition is set correctly' do
|
||||
allow(search_navigation).to receive(:feature_flag_tab_enabled?)
|
||||
.with(:global_search_wiki_tab).and_return(feature_flag_enabled)
|
||||
allow(search_navigation).to receive(:tab_enabled_for_project?).with(:wiki_blobs).and_return(tab_enabled)
|
||||
allow(search_navigation).to receive(:tab_enabled_for_project?)
|
||||
.with(:wiki_blobs).and_return(tab_enabled_for_project)
|
||||
|
||||
expect(tabs[:wiki_blobs][:condition]).to eq(condition)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,13 +2,15 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Integrations::Teamcity, :use_clean_rails_memory_store_caching do
|
||||
RSpec.describe Integrations::Teamcity, :use_clean_rails_memory_store_caching, feature_category: :integrations do
|
||||
include ReactiveCachingHelpers
|
||||
include StubRequests
|
||||
|
||||
let(:teamcity_url) { 'https://gitlab.teamcity.com' }
|
||||
let(:teamcity_full_url) { 'https://gitlab.teamcity.com/httpAuth/app/rest/builds/branch:unspecified:any,revision:123' }
|
||||
let_it_be(:project) { create(:project) }
|
||||
let(:teamcity_full_url) { 'https://gitlab.teamcity.com/httpAuth/app/rest/builds/branch:unspecified:any,revision:123' }
|
||||
let(:teamcity_url) { 'https://gitlab.teamcity.com' }
|
||||
|
||||
it_behaves_like Integrations::HasAvatar
|
||||
|
||||
subject(:integration) do
|
||||
described_class.create!(
|
||||
|
|
@ -305,6 +307,14 @@ RSpec.describe Integrations::Teamcity, :use_clean_rails_memory_store_caching do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#attribution_notice' do
|
||||
it do
|
||||
expect(subject.attribution_notice)
|
||||
.to eq('Copyright © 2024 JetBrains s.r.o. JetBrains TeamCity and the JetBrains TeamCity logo are registered ' \
|
||||
'trademarks of JetBrains s.r.o.')
|
||||
end
|
||||
end
|
||||
|
||||
def stub_post_to_build_queue(branch:)
|
||||
teamcity_full_url = "#{teamcity_url}/httpAuth/app/rest/buildQueue"
|
||||
body ||= %(<build branchName=\"#{branch}\"><buildType id=\"foo\"/></build>)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Integrations::Youtrack do
|
||||
RSpec.describe Integrations::Youtrack, feature_category: :integrations do
|
||||
it_behaves_like Integrations::HasAvatar
|
||||
|
||||
describe 'Validations' do
|
||||
context 'when integration is active' do
|
||||
before do
|
||||
|
|
@ -43,4 +45,12 @@ RSpec.describe Integrations::Youtrack do
|
|||
expect(subject.fields.pluck(:name)).to eq(%w[project_url issues_url])
|
||||
end
|
||||
end
|
||||
|
||||
describe '#attribution_notice' do
|
||||
it do
|
||||
expect(subject.attribution_notice)
|
||||
.to eq('Copyright © 2024 JetBrains s.r.o. JetBrains YouTrack and the JetBrains YouTrack logo are registered ' \
|
||||
'trademarks of JetBrains s.r.o.')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -220,6 +220,12 @@ RSpec.describe 'Update a work item', feature_category: :team_planning do
|
|||
let(:mutation_work_item) { group_work_item }
|
||||
|
||||
it_behaves_like 'mutation updating work item labels'
|
||||
|
||||
context 'with quick action' do
|
||||
let(:input) { { 'descriptionWidget' => { 'description' => "/remove_label ~\"#{existing_label.name}\"" } } }
|
||||
|
||||
it_behaves_like 'mutation updating work item labels'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -241,6 +247,14 @@ RSpec.describe 'Update a work item', feature_category: :team_planning do
|
|||
let(:mutation_work_item) { group_work_item }
|
||||
|
||||
it_behaves_like 'mutation updating work item labels'
|
||||
|
||||
context 'with quick action' do
|
||||
let(:input) do
|
||||
{ 'descriptionWidget' => { 'description' => "/labels ~\"#{label1.name}\" ~\"#{label2.name}\"" } }
|
||||
end
|
||||
|
||||
it_behaves_like 'mutation updating work item labels'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -113,7 +113,8 @@ RSpec.describe Packages::Pypi::CreatePackageService, :aggregate_failures, featur
|
|||
context 'with an invalid metadata' do
|
||||
let(:requires_python) { 'x' * 256 }
|
||||
|
||||
it_behaves_like 'returning an error service response', /Pypi package metadata invalid/ do
|
||||
it_behaves_like 'returning an error service response',
|
||||
message: 'Validation failed: Required python is too long (maximum is 255 characters)' do
|
||||
it { is_expected.to have_attributes(reason: :invalid_parameter) }
|
||||
end
|
||||
end
|
||||
|
|
@ -142,7 +143,8 @@ RSpec.describe Packages::Pypi::CreatePackageService, :aggregate_failures, featur
|
|||
params[:md5_digest] = md5
|
||||
end
|
||||
|
||||
it_behaves_like 'returning an error service response', /File name has already been taken/ do
|
||||
it_behaves_like 'returning an error service response',
|
||||
message: 'Validation failed: File name has already been taken' do
|
||||
it { is_expected.to have_attributes(reason: :invalid_parameter) }
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -20,8 +20,9 @@ RSpec.describe QuickActions::InterpretService, feature_category: :team_planning
|
|||
let(:milestone) { create(:milestone, project: project, title: '9.10') }
|
||||
let(:commit) { create(:commit, project: project) }
|
||||
let(:current_user) { developer }
|
||||
let(:container) { project }
|
||||
|
||||
subject(:service) { described_class.new(project, current_user) }
|
||||
subject(:service) { described_class.new(container: container, current_user: current_user) }
|
||||
|
||||
before_all do
|
||||
public_project.add_developer(developer)
|
||||
|
|
@ -860,8 +861,14 @@ RSpec.describe QuickActions::InterpretService, feature_category: :team_planning
|
|||
end
|
||||
|
||||
context 'merge command' do
|
||||
let(:service) { described_class.new(project, developer, { merge_request_diff_head_sha: merge_request.diff_head_sha }) }
|
||||
let(:merge_request) { create(:merge_request, source_project: repository_project) }
|
||||
let(:service) do
|
||||
described_class.new(
|
||||
container: project,
|
||||
current_user: developer,
|
||||
params: { merge_request_diff_head_sha: merge_request.diff_head_sha }
|
||||
)
|
||||
end
|
||||
|
||||
it_behaves_like 'merge immediately command' do
|
||||
let(:content) { '/merge' }
|
||||
|
|
@ -881,7 +888,7 @@ RSpec.describe QuickActions::InterpretService, feature_category: :team_planning
|
|||
end
|
||||
|
||||
context 'can not be merged when logged user does not have permissions' do
|
||||
let(:service) { described_class.new(project, create(:user)) }
|
||||
let(:service) { described_class.new(container: project, current_user: create(:user)) }
|
||||
|
||||
it_behaves_like 'failed command', 'Could not apply merge command.' do
|
||||
let(:content) { "/merge" }
|
||||
|
|
@ -890,7 +897,13 @@ RSpec.describe QuickActions::InterpretService, feature_category: :team_planning
|
|||
end
|
||||
|
||||
context 'can not be merged when sha does not match' do
|
||||
let(:service) { described_class.new(project, developer, { merge_request_diff_head_sha: 'othersha' }) }
|
||||
let(:service) do
|
||||
described_class.new(
|
||||
container: project,
|
||||
current_user: developer,
|
||||
params: { merge_request_diff_head_sha: 'othersha' }
|
||||
)
|
||||
end
|
||||
|
||||
it_behaves_like 'failed command', 'Branch has been updated since the merge was requested.' do
|
||||
let(:content) { "/merge" }
|
||||
|
|
@ -900,7 +913,7 @@ RSpec.describe QuickActions::InterpretService, feature_category: :team_planning
|
|||
|
||||
context 'when sha is missing' do
|
||||
let(:project) { repository_project }
|
||||
let(:service) { described_class.new(project, developer, {}) }
|
||||
let(:service) { described_class.new(container: project, current_user: developer) }
|
||||
|
||||
it_behaves_like 'failed command', 'The `/merge` quick action requires the SHA of the head of the branch.' do
|
||||
let(:content) { "/merge" }
|
||||
|
|
@ -1607,7 +1620,7 @@ RSpec.describe QuickActions::InterpretService, feature_category: :team_planning
|
|||
end
|
||||
|
||||
context 'when non-member is creating a new issue' do
|
||||
let(:service) { described_class.new(project, create(:user)) }
|
||||
let(:service) { described_class.new(container: project, current_user: create(:user)) }
|
||||
|
||||
it_behaves_like 'confidential command' do
|
||||
let(:content) { '/confidential' }
|
||||
|
|
@ -1697,6 +1710,33 @@ RSpec.describe QuickActions::InterpretService, feature_category: :team_planning
|
|||
end
|
||||
end
|
||||
|
||||
context '/label command' do
|
||||
context 'when target is a group level work item' do
|
||||
let_it_be(:new_group) { create(:group).tap { |g| g.add_developer(developer) } }
|
||||
let_it_be(:group_level_work_item) { create(:work_item, :group_level, namespace: new_group) }
|
||||
# this label should not be show on the list as belongs to another group
|
||||
let_it_be(:invalid_label) { create(:group_label, title: 'not_from_group', group: group) }
|
||||
let(:container) { new_group }
|
||||
|
||||
# This spec was introduced just to validate that the label finder scopes que query to a single group.
|
||||
# The command checks that labels are available as part of the condition.
|
||||
# Query was timing out in .com https://gitlab.com/gitlab-org/gitlab/-/issues/441123
|
||||
it 'is not available when there are no labels associated with the group' do
|
||||
expect(service.available_commands(group_level_work_item)).not_to include(a_hash_including(name: :label))
|
||||
end
|
||||
|
||||
context 'when a label exists at the group level' do
|
||||
before do
|
||||
create(:group_label, group: new_group)
|
||||
end
|
||||
|
||||
it 'is available' do
|
||||
expect(service.available_commands(group_level_work_item)).to include(a_hash_including(name: :label))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context '/copy_metadata command' do
|
||||
let(:todo_label) { create(:label, project: project, title: 'To Do') }
|
||||
let(:inreview_label) { create(:label, project: project, title: 'In Review') }
|
||||
|
|
@ -1707,7 +1747,7 @@ RSpec.describe QuickActions::InterpretService, feature_category: :team_planning
|
|||
|
||||
context 'when the user does not have permission' do
|
||||
let(:guest) { create(:user) }
|
||||
let(:service) { described_class.new(project, guest) }
|
||||
let(:service) { described_class.new(container: project, current_user: guest) }
|
||||
|
||||
it 'is not available' do
|
||||
expect(service.available_commands(issue)).not_to include(a_hash_including(name: :copy_metadata))
|
||||
|
|
@ -1814,7 +1854,7 @@ RSpec.describe QuickActions::InterpretService, feature_category: :team_planning
|
|||
context 'when current_user cannot :admin_issue' do
|
||||
let(:visitor) { create(:user) }
|
||||
let(:issue) { create(:issue, project: project, author: visitor) }
|
||||
let(:service) { described_class.new(project, visitor) }
|
||||
let(:service) { described_class.new(container: project, current_user: visitor) }
|
||||
|
||||
it_behaves_like 'failed command', 'Could not apply assign command.' do
|
||||
let(:content) { "/assign @#{developer.username}" }
|
||||
|
|
@ -1959,7 +1999,7 @@ RSpec.describe QuickActions::InterpretService, feature_category: :team_planning
|
|||
context '/target_branch command' do
|
||||
let(:non_empty_project) { create(:project, :repository) }
|
||||
let(:another_merge_request) { create(:merge_request, author: developer, source_project: non_empty_project) }
|
||||
let(:service) { described_class.new(non_empty_project, developer) }
|
||||
let(:service) { described_class.new(container: non_empty_project, current_user: developer) }
|
||||
|
||||
it 'updates target_branch if /target_branch command is executed' do
|
||||
_, updates, _ = service.execute('/target_branch merge-test', merge_request)
|
||||
|
|
@ -2164,7 +2204,7 @@ RSpec.describe QuickActions::InterpretService, feature_category: :team_planning
|
|||
|
||||
context 'when logged user cannot push code to the project' do
|
||||
let(:project) { create(:project, :private) }
|
||||
let(:service) { described_class.new(project, create(:user)) }
|
||||
let(:service) { described_class.new(container: project, current_user: create(:user)) }
|
||||
|
||||
it_behaves_like 'failed command', 'Could not apply create_merge_request command.'
|
||||
end
|
||||
|
|
@ -2892,7 +2932,7 @@ RSpec.describe QuickActions::InterpretService, feature_category: :team_planning
|
|||
end
|
||||
|
||||
describe '#explain' do
|
||||
let(:service) { described_class.new(project, developer) }
|
||||
let(:service) { described_class.new(container: project, current_user: developer) }
|
||||
let(:merge_request) { create(:merge_request, source_project: project) }
|
||||
|
||||
describe 'close command' do
|
||||
|
|
@ -3477,7 +3517,7 @@ RSpec.describe QuickActions::InterpretService, feature_category: :team_planning
|
|||
end
|
||||
|
||||
context 'when user cannot admin link' do
|
||||
subject(:service) { described_class.new(project, create(:user)) }
|
||||
subject(:service) { described_class.new(container: project, current_user: create(:user)) }
|
||||
|
||||
it_behaves_like 'command is not available'
|
||||
end
|
||||
|
|
@ -3526,7 +3566,7 @@ RSpec.describe QuickActions::InterpretService, feature_category: :team_planning
|
|||
end
|
||||
|
||||
context 'when user cannot admin link' do
|
||||
subject(:service) { described_class.new(project, create(:user)) }
|
||||
subject(:service) { described_class.new(container: project, current_user: create(:user)) }
|
||||
|
||||
it_behaves_like 'command is not available'
|
||||
end
|
||||
|
|
@ -3573,7 +3613,7 @@ RSpec.describe QuickActions::InterpretService, feature_category: :team_planning
|
|||
end
|
||||
|
||||
context 'when user cannot admin link' do
|
||||
subject(:service) { described_class.new(project, create(:user)) }
|
||||
subject(:service) { described_class.new(container: project, current_user: create(:user)) }
|
||||
|
||||
it_behaves_like 'command is not available'
|
||||
end
|
||||
|
|
@ -3591,7 +3631,7 @@ RSpec.describe QuickActions::InterpretService, feature_category: :team_planning
|
|||
let_it_be(:guest) { create(:user) }
|
||||
|
||||
let(:issue) { build(:issue, project: public_project) }
|
||||
let(:service) { described_class.new(project, guest) }
|
||||
let(:service) { described_class.new(container: project, current_user: guest) }
|
||||
|
||||
before_all do
|
||||
public_project.add_guest(guest)
|
||||
|
|
|
|||
|
|
@ -226,7 +226,7 @@ RSpec.configure do |config|
|
|||
# Gradually stop using rspec-retry
|
||||
# See https://gitlab.com/gitlab-org/gitlab/-/issues/438388
|
||||
%i[
|
||||
lib mailers metrics_server
|
||||
graphql haml_lint helpers initializers keeps lib mailers metrics_server
|
||||
migrations models policies presenters rack_servers requests
|
||||
routing rubocop scripts serializers services sidekiq sidekiq_cluster
|
||||
spam support_specs tasks tooling uploaders validators views workers
|
||||
|
|
|
|||
|
|
@ -2,16 +2,16 @@
|
|||
|
||||
module Features
|
||||
module DomHelpers
|
||||
def has_testid?(testid, **kwargs)
|
||||
page.has_selector?("[data-testid='#{testid}']", **kwargs)
|
||||
def has_testid?(testid, context: page, **kwargs)
|
||||
context.has_selector?("[data-testid='#{testid}']", **kwargs)
|
||||
end
|
||||
|
||||
def find_by_testid(testid, **kwargs)
|
||||
page.find("[data-testid='#{testid}']", **kwargs)
|
||||
def find_by_testid(testid, context: page, **kwargs)
|
||||
context.find("[data-testid='#{testid}']", **kwargs)
|
||||
end
|
||||
|
||||
def within_testid(testid, **kwargs, &block)
|
||||
page.within("[data-testid='#{testid}']", **kwargs, &block)
|
||||
def within_testid(testid, context: page, **kwargs, &block)
|
||||
context.within("[data-testid='#{testid}']", **kwargs, &block)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Reference in New Issue