Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2023-12-06 21:12:50 +00:00
parent bf03e90e02
commit aa74b7b4c5
34 changed files with 414 additions and 175 deletions

View File

@ -26,7 +26,6 @@ export const initJobDetails = () => {
subscriptionsMoreMinutesUrl,
endpoint,
pagePath,
buildStatus,
projectPath,
retryOutdatedJobDocsUrl,
aiRootCauseAnalysisAvailable,
@ -55,10 +54,6 @@ export const initJobDetails = () => {
deploymentHelpUrl,
runnerSettingsUrl,
subscriptionsMoreMinutesUrl,
endpoint,
pagePath,
buildStatus,
projectPath,
},
});
},

View File

@ -56,15 +56,6 @@ export default {
required: false,
default: null,
},
terminalPath: {
type: String,
required: false,
default: null,
},
projectPath: {
type: String,
required: true,
},
subscriptionsMoreMinutesUrl: {
type: String,
required: false,
@ -261,7 +252,6 @@ export default {
v-if="shouldRenderSharedRunnerLimitWarning"
:quota-used="job.runners.quota.used"
:quota-limit="job.runners.quota.limit"
:project-path="projectPath"
:subscriptions-more-minutes-url="subscriptionsMoreMinutesUrl"
/>

View File

@ -0,0 +1,71 @@
# frozen_string_literal: true
module Resolvers
class WorkItemReferencesResolver < BaseResolver
prepend ::WorkItems::LookAheadPreloads
include Gitlab::Graphql::Authorize::AuthorizeResource
REFERENCES_LIMIT = 10
authorize :read_work_item
type ::Types::WorkItemType.connection_type, null: true
argument :context_namespace_path, GraphQL::Types::ID,
required: false,
description: 'Full path of the context namespace (project or group).'
argument :refs, [GraphQL::Types::String], required: true,
description: 'Work item references. Can be either a short reference or URL.'
def ready?(**args)
if args[:refs].size > REFERENCES_LIMIT
raise Gitlab::Graphql::Errors::ArgumentError,
format(
_('Number of references exceeds the limit. ' \
'Please provide no more than %{refs_limit} references at the same time.'),
refs_limit: REFERENCES_LIMIT
)
end
super
end
def resolve_with_lookahead(context_namespace_path: nil, refs: [])
return WorkItem.none if refs.empty?
@container = authorized_find!(context_namespace_path)
# Only ::Project is supported at the moment, future iterations will include ::Group.
# See https://gitlab.com/gitlab-org/gitlab/-/issues/432555
return WorkItem.none if container.is_a?(::Group)
apply_lookahead(find_work_items(refs))
end
private
attr_reader :container
# rubocop: disable CodeReuse/ActiveRecord -- #references is not an ActiveRecord method
def find_work_items(references)
links, short_references = references.partition { |r| r.include?('/work_items/') }
item_ids = references_extractor(short_references).references(:issue, ids_only: true)
item_ids << references_extractor(links).references(:work_item, ids_only: true) if links.any?
WorkItem.id_in(item_ids.flatten)
end
# rubocop: enable CodeReuse/ActiveRecord
def references_extractor(refs)
extractor = ::Gitlab::ReferenceExtractor.new(container, context[:current_user])
extractor.analyze(refs.join(' '), {})
extractor
end
def find_object(full_path)
Routable.find_by_full_path(full_path)
end
end
end

View File

@ -216,6 +216,13 @@ module Types
description: 'Find machine learning models.',
resolver: Resolvers::Ml::ModelDetailResolver
field :work_items_by_reference,
null: true,
alpha: { milestone: '16.7' },
description: 'Find work items by their reference.',
extras: [:lookahead],
resolver: Resolvers::WorkItemReferencesResolver
def design_management
DesignManagementObject.new(nil)
end

View File

@ -10,8 +10,6 @@ module Ci
"artifact_help_url" => help_page_path('user/gitlab_com/index.md', anchor: 'gitlab-cicd'),
"deployment_help_url" => help_page_path('user/project/clusters/deploy_to_cluster.md', anchor: 'troubleshooting'),
"runner_settings_url" => project_runners_path(build.project, anchor: 'js-runners-settings'),
"build_status" => build.status,
"build_stage" => build.stage_name,
"retry_outdated_job_docs_url" => help_page_path('ci/pipelines/settings', anchor: 'retry-outdated-jobs'),
"test_report_summary_url" => test_report_summary_project_job_path(project, build, format: :json),
"pipeline_test_report_url" => test_report_project_pipeline_path(project, build.pipeline)

View File

@ -38,36 +38,32 @@ module UpdateRepositoryStorageWorker
container_id ||= repository_storage_move.container_id
if Feature.enabled?(:use_lock_for_update_repository_storage)
# Use exclusive lock to prevent multiple storage migrations at the same time
#
# Note: instead of using a randomly generated `uuid`, we provide a worker jid value.
# That will allow to track a worker that requested a lease.
lease_key = [self.class.name.underscore, container_id].join(':')
exclusive_lease = Gitlab::ExclusiveLease.new(lease_key, uuid: jid, timeout: LEASE_TIMEOUT)
lease = exclusive_lease.try_obtain
# Use exclusive lock to prevent multiple storage migrations at the same time
#
# Note: instead of using a randomly generated `uuid`, we provide a worker jid value.
# That will allow to track a worker that requested a lease.
lease_key = [self.class.name.underscore, container_id].join(':')
exclusive_lease = Gitlab::ExclusiveLease.new(lease_key, uuid: jid, timeout: LEASE_TIMEOUT)
lease = exclusive_lease.try_obtain
if lease
begin
update_repository_storage(repository_storage_move)
ensure
exclusive_lease.cancel
end
else
# If there is an ungoing storage migration, then the current one should be marked as failed
repository_storage_move.do_fail!
# A special case
# Sidekiq can receive an interrupt signal during the processing.
# It kills existing workers and reschedules their jobs using the same jid.
# But it can cause a situation when the migration is only half complete (see https://gitlab.com/gitlab-org/gitlab/-/issues/429049#note_1635650597)
#
# Here we detect this case and release the lock.
uuid = Gitlab::ExclusiveLease.get_uuid(lease_key)
exclusive_lease.cancel if uuid == jid
if lease
begin
update_repository_storage(repository_storage_move)
ensure
exclusive_lease.cancel
end
else
update_repository_storage(repository_storage_move)
# If there is an ungoing storage migration, then the current one should be marked as failed
repository_storage_move.do_fail!
# A special case
# Sidekiq can receive an interrupt signal during the processing.
# It kills existing workers and reschedules their jobs using the same jid.
# But it can cause a situation when the migration is only half complete (see https://gitlab.com/gitlab-org/gitlab/-/issues/429049#note_1635650597)
#
# Here we detect this case and release the lock.
uuid = Gitlab::ExclusiveLease.get_uuid(lease_key)
exclusive_lease.cancel if uuid == jid
end
end

View File

@ -1,8 +0,0 @@
---
name: use_lock_for_update_repository_storage
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/136169
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/431198
milestone: '16.6'
type: development
group: group::source code
default_enabled: false

View File

@ -4,3 +4,4 @@ description: Updates default value of code_suggestions on namespace_settings tab
feature_category: code_suggestions
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/121126
milestone: '16.1'
finalized_by: '20231206145850'

View File

@ -0,0 +1,16 @@
# frozen_string_literal: true
class DropUniqueIndexJobIdFileTypeToCiJobArtifact < Gitlab::Database::Migration[2.2]
milestone '16.7'
disable_ddl_transaction!
TABLE_NAME = :ci_job_artifacts
INDEX_NAME = :index_ci_job_artifacts_on_job_id_and_file_type
def up
remove_concurrent_index_by_name(TABLE_NAME, INDEX_NAME)
end
def down
add_concurrent_index(TABLE_NAME, %i[job_id file_type], unique: true, name: INDEX_NAME)
end
end

View File

@ -0,0 +1,21 @@
# frozen_string_literal: true
class FinalizeBackfillCodeSuggestionsNamespaceSettings < Gitlab::Database::Migration[2.2]
milestone '16.7'
disable_ddl_transaction!
restrict_gitlab_migration gitlab_schema: :gitlab_main_cell
def up
ensure_batched_background_migration_is_finished(
job_class_name: 'BackfillCodeSuggestionsNamespaceSettings',
table_name: :namespace_settings,
column_name: :namespace_id,
job_arguments: [],
finalize: true
)
end
def down; end
end

View File

@ -0,0 +1 @@
5ca9bd14a7c69b4b77745303c47c7d11890f3ced97c8a1a68b5b713b29a2dab7

View File

@ -0,0 +1 @@
53442f9c3ef0e0f3f31b4be177faf3d073ee8b74d20ede7a1673bedfa097f0b9

View File

@ -32155,8 +32155,6 @@ CREATE INDEX index_ci_job_artifacts_on_id_project_id_and_created_at ON ci_job_ar
CREATE INDEX index_ci_job_artifacts_on_id_project_id_and_file_type ON ci_job_artifacts USING btree (project_id, file_type, id);
CREATE UNIQUE INDEX index_ci_job_artifacts_on_job_id_and_file_type ON ci_job_artifacts USING btree (job_id, file_type);
CREATE INDEX index_ci_job_artifacts_on_partition_id_job_id ON ci_job_artifacts USING btree (partition_id, job_id);
CREATE INDEX index_ci_job_artifacts_on_project_id ON ci_job_artifacts USING btree (project_id);

View File

@ -1033,6 +1033,27 @@ Returns [`WorkItem`](#workitem).
| ---- | ---- | ----------- |
| <a id="queryworkitemid"></a>`id` | [`WorkItemID!`](#workitemid) | Global ID of the work item. |
### `Query.workItemsByReference`
Find work items by their reference.
WARNING:
**Introduced** in 16.7.
This feature is an Experiment. It can be changed or removed at any time.
Returns [`WorkItemConnection`](#workitemconnection).
This field returns a [connection](#connections). It accepts the
four standard [pagination arguments](#connection-pagination-arguments):
`before: String`, `after: String`, `first: Int`, `last: Int`.
#### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="queryworkitemsbyreferencecontextnamespacepath"></a>`contextNamespacePath` | [`ID`](#id) | Full path of the context namespace (project or group). |
| <a id="queryworkitemsbyreferencerefs"></a>`refs` | [`[String!]!`](#string) | Work item references. Can be either a short reference or URL. |
### `Query.workspace`
Find a workspace.

View File

@ -16,16 +16,18 @@ Beta users should read about the [known limitations](#known-limitations). We loo
Write code more efficiently by using generative AI to suggest code while you're developing.
Code Suggestions supports two distinct types of interactions:
With Code Suggestions, you get:
- Code Completion, which suggests completions the current line you are typing. These suggestions are usually low latency.
- Code Generation, which generates code based on a natural language code comment block. Generating code can exceed multiple seconds.
## Start using Code Suggestions
GitLab Duo Code Suggestions are available:
- On [self-managed](self_managed.md) and [SaaS](saas.md).
- On [self-managed](self_managed.md) and [SaaS](saas.md). View these pages to get started.
- In VS Code, Microsoft Visual Studio, JetBrains IDEs, and Neovim. You must have the corresponding GitLab extension installed.
- In the GitLab WebIDE.
- In the GitLab Web IDE.
<div class="video-fallback">
<a href="https://youtu.be/wAYiy05fjF0">View how to setup and use GitLab Duo Code Suggestions</a>.
@ -37,36 +39,6 @@ GitLab Duo Code Suggestions are available:
During Beta, usage of Code Suggestions is governed by the [GitLab Testing Agreement](https://about.gitlab.com/handbook/legal/testing-agreement/).
Learn about [data usage when using Code Suggestions](#code-suggestions-data-usage). As Code Suggestions matures to General Availability it will be governed by our [AI Functionality Terms](https://about.gitlab.com/handbook/legal/ai-functionality-terms/).
## Use Code Suggestions
Prerequisites:
- You must have installed and configured a [supported IDE editor extension](index.md#supported-editor-extensions).
- If you are a **SaaS** user, you must enable Code Suggestions for:
- [The top-level group](../../../group/manage.md#enable-code-suggestions) (you must have the Owner role for that group).
- [Your own account](../../../profile/preferences.md#enable-code-suggestions).
- If you are a **self-managed** user, you must enable Code Suggestions [for your instance](self_managed.md#enable-code-suggestions-on-self-managed-gitlab). How you enable Code Suggestions differs depending on your version of GitLab.
To use Code Suggestions:
1. Author your code. As you type, suggestions are displayed. Code Suggestions, depending on the cursor position, either provides code snippets or completes the current line.
1. Describe the requirements in natural language. Be concise and specific. Code Suggestions generates functions and code snippets as appropriate.
1. To accept a suggestion, press <kbd>Tab</kbd>.
1. To ignore a suggestion, keep typing as you usually would.
1. To explicitly reject a suggestion, press <kbd>esc</kbd>.
Things to remember:
- AI is non-deterministic, so you may not get the same suggestion every time with the same input.
- Just like product requirements, writing clear, descriptive, and specific tasks results in quality generated code.
### Progressive enhancement
This feature is designed as a progressive enhancement to developer's IDEs.
Code Suggestions offer a completion if a suitable recommendation is provided to the user in a timely matter.
In the event of a connection issue or model inference failure, the feature gracefully degrades.
Code Suggestions do not prevent you from writing code in your IDE.
## Supported languages
Code Suggestions support is a function of the:
@ -170,6 +142,13 @@ However, Code Suggestions may generate suggestions that are:
- Insecure code
- Offensive or insensitive
## Progressive enhancement
This feature is designed as a progressive enhancement to developer's IDEs.
Code Suggestions offer a completion if a suitable recommendation is provided to the user in a timely matter.
In the event of a connection issue or model inference failure, the feature gracefully degrades.
Code Suggestions do not prevent you from writing code in your IDE.
## Feedback
Report issues in the [feedback issue](https://gitlab.com/gitlab-org/gitlab/-/issues/405152).

View File

@ -34,7 +34,21 @@ If you are having issues enabling Code Suggestions, view the
Prerequisites:
- Ensure Code Suggestions is enabled for your user and group.
- You must have installed and configured a [supported IDE editor extension](index.md#supported-editor-extensions).
- You must have a [supported IDE editor extension](index.md#supported-editor-extensions).
- Code Suggestions must be enabled for:
- [The top-level group](../../../group/manage.md#enable-code-suggestions).
- [Your own account](../../../profile/preferences.md#enable-code-suggestions).
[Use Code Suggestions](index.md#use-code-suggestions).
To use Code Suggestions:
1. Author your code. As you type, suggestions are displayed.
Code Suggestions provide code snippets or complete the current line, depending on the cursor position.
1. Describe the requirements in natural language. Be concise and specific. Code Suggestions generates functions and code snippets as appropriate.
1. To accept a suggestion, press <kbd>Tab</kbd>.
1. To ignore a suggestion, keep typing as you usually would.
1. To explicitly reject a suggestion, press <kbd>Esc</kbd>.
Things to remember:
- AI is non-deterministic, so you may not get the same suggestion every time with the same input.
- Just like product requirements, writing clear, descriptive, and specific tasks results in quality generated code.

View File

@ -151,10 +151,22 @@ Without the manual synchronization, it might take up to 24 hours to active Code
Prerequisites:
- Code Suggestions must be enabled [for the instance](#enable-code-suggestions-on-self-managed-gitlab).
- You must have installed and configured a [supported IDE editor extension](index.md#supported-editor-extensions).
- You must have a [supported IDE editor extension](index.md#supported-editor-extensions).
- Code Suggestions must be enabled [for your instance](self_managed.md#enable-code-suggestions-on-self-managed-gitlab).
[Use Code Suggestions](index.md#use-code-suggestions).
To use Code Suggestions:
1. Author your code. As you type, suggestions are displayed.
Code Suggestions provide code snippets or complete the current line, depending on the cursor position.
1. Describe the requirements in natural language. Be concise and specific. Code Suggestions generates functions and code snippets as appropriate.
1. To accept a suggestion, press <kbd>Tab</kbd>.
1. To ignore a suggestion, keep typing as you usually would.
1. To explicitly reject a suggestion, press <kbd>Esc</kbd>.
Things to remember:
- AI is non-deterministic, so you may not get the same suggestion every time with the same input.
- Just like product requirements, writing clear, descriptive, and specific tasks results in quality generated code.
### Data privacy

View File

@ -57,7 +57,7 @@ module API
end
def run_id
""
object.candidate.eid
end
def status

View File

@ -5,6 +5,8 @@ module API
module Ml
module Mlflow
class RunInfo < Grape::Entity
include ::API::Helpers::RelatedResourcesHelpers
expose :run_id
expose :run_id, as: :run_uuid
expose(:experiment_id) { |candidate| candidate.experiment.iid.to_s }
@ -12,7 +14,7 @@ module API
expose :end_time, expose_nil: false
expose :name, as: :run_name, expose_nil: false
expose(:status) { |candidate| candidate.status.to_s.upcase }
expose(:artifact_uri) { |candidate, options| "#{options[:packages_url]}#{candidate.artifact_root}" }
expose :artifact_uri
expose(:lifecycle_stage) { |candidate| 'active' }
expose(:user_id) { |candidate| candidate.user_id.to_s }
@ -21,6 +23,34 @@ module API
def run_id
object.eid.to_s
end
def artifact_uri
expose_url(model_version_uri || generic_package_uri)
end
# Example: http://127.0.0.1:3000/api/v4/projects/20/packages/ml_models/my-model-name-4/3.0.0
def model_version_uri
return unless object.model_version_id
model_version = object.model_version
path = api_v4_projects_packages_ml_models_model_version_path(
id: object.project.id, model_name: model_version.model.name, model_version: '', file_name: ''
)
path.sub('/model_version', "/#{model_version.version}")
end
# Example: http://127.0.0.1:3000/api/v4/projects/20/packages/generic/ml_experiment_1/1/
# Note: legacy format
def generic_package_uri
path = api_v4_projects_packages_generic_package_version_path(
id: object.project.id, package_name: '', file_name: ''
)
path = path.delete_suffix('/package_version')
[path, object.artifact_root].join('')
end
end
end
end

View File

@ -132,15 +132,6 @@ module API
def model
@model ||= find_model(user_project, params[:name])
end
def packages_url
path = api_v4_projects_packages_generic_package_version_path(
id: user_project.id, package_name: '', file_name: ''
)
path = path.delete_suffix('/package_version')
expose_url(path)
end
end
end
end

View File

@ -31,7 +31,7 @@ module API
end
post 'create', urgency: :low do
present candidate_repository.create!(experiment, params[:start_time], params[:tags], params[:run_name]),
with: Entities::Ml::Mlflow::GetRun, packages_url: packages_url
with: Entities::Ml::Mlflow::GetRun
end
desc 'Gets an MLFlow Run, which maps to GitLab Candidates' do
@ -43,7 +43,7 @@ module API
optional :run_uuid, type: String, desc: 'This parameter is ignored'
end
get 'get', urgency: :low do
present candidate, with: Entities::Ml::Mlflow::GetRun, packages_url: packages_url
present candidate, with: Entities::Ml::Mlflow::GetRun
end
desc 'Searches runs/candidates within a project' do
@ -83,7 +83,7 @@ module API
next_page_token: paginator.cursor_for_next_page
}
present result, with: Entities::Ml::Mlflow::SearchRuns, packages_url: packages_url
present result, with: Entities::Ml::Mlflow::SearchRuns
end
desc 'Updates a Run.' do
@ -101,7 +101,7 @@ module API
post 'update', urgency: :low do
candidate_repository.update(candidate, params[:status], params[:end_time])
present candidate, with: Entities::Ml::Mlflow::UpdateRun, packages_url: packages_url
present candidate, with: Entities::Ml::Mlflow::UpdateRun
end
desc 'Logs a metric to a run.' do

View File

@ -15,7 +15,7 @@ module ExtractsRef
class << self
def ref_type(type)
return unless REF_TYPES.include?(type&.downcase)
return unless REF_TYPES.include?(type.to_s.downcase)
type.downcase
end

View File

@ -32845,6 +32845,9 @@ msgstr ""
msgid "Number of files touched"
msgstr ""
msgid "Number of references exceeds the limit. Please provide no more than %{refs_limit} references at the same time."
msgstr ""
msgid "Number of replicas"
msgstr ""

View File

@ -6,7 +6,10 @@ module RuboCop
class IDType < RuboCop::Cop::Base
MSG = 'Do not use GraphQL::Types::ID, use a specific GlobalIDType instead'
ALLOWLISTED_ARGUMENTS = %i[iid full_path project_path group_path target_project_path namespace_path].freeze
ALLOWLISTED_ARGUMENTS = %i[
iid full_path project_path group_path target_project_path namespace_path
context_namespace_path
].freeze
def_node_search :graphql_id_type?, <<~PATTERN
(send nil? :argument (_ #does_not_match?) (const (const (const nil? :GraphQL) :Types) :ID) ...)

View File

@ -25,8 +25,6 @@ RSpec.describe Ci::JobsHelper, feature_category: :continuous_integration do
"artifact_help_url" => "/help/user/gitlab_com/index.md#gitlab-cicd",
"deployment_help_url" => "/help/user/project/clusters/deploy_to_cluster.md#troubleshooting",
"runner_settings_url" => "/#{project.full_path}/-/runners#js-runners-settings",
"build_status" => "pending",
"build_stage" => "test",
"retry_outdated_job_docs_url" => "/help/ci/pipelines/settings#retry-outdated-jobs",
"test_report_summary_url" => "/#{project.full_path}/-/jobs/#{job.id}/test_report_summary.json",
"pipeline_test_report_url" => "/#{project.full_path}/-/pipelines/#{job.pipeline.id}/test_report"

View File

@ -3,9 +3,9 @@
require 'spec_helper'
RSpec.describe API::Entities::Ml::Mlflow::RunInfo, feature_category: :mlops do
let_it_be(:candidate) { build(:ml_candidates) }
let_it_be(:candidate) { build_stubbed(:ml_candidates, internal_id: 1) }
subject { described_class.new(candidate, packages_url: 'http://example.com').as_json }
subject { described_class.new(candidate).as_json }
context 'when start_time is nil' do
it { expect(subject[:start_time]).to eq(0) }
@ -66,8 +66,19 @@ RSpec.describe API::Entities::Ml::Mlflow::RunInfo, feature_category: :mlops do
end
describe 'artifact_uri' do
it 'is not implemented' do
expect(subject[:artifact_uri]).to eq("http://example.com#{candidate.artifact_root}")
context 'when candidate does not belong to a model version' do
it 'returns the generic package (legacy) format of the artifact_uri' do
expect(subject[:artifact_uri]).to eq("http://localhost/api/v4/projects/#{candidate.project_id}/packages/generic#{candidate.artifact_root}")
end
end
context 'when candidate belongs to a model version' do
let!(:version) { create(:ml_model_versions, :with_package) }
let!(:candidate) { version.candidate }
it 'returns the model version format of the artifact_uri' do
expect(subject[:artifact_uri]).to eq("http://localhost/api/v4/projects/#{candidate.project_id}/packages/ml_models/#{version.model.name}/#{version.version}")
end
end
end

View File

@ -5,39 +5,6 @@ require 'spec_helper'
RSpec.describe API::Ml::Mlflow::ApiHelpers, feature_category: :mlops do
include described_class
describe '#packages_url' do
subject { packages_url }
let_it_be(:user_project) { build_stubbed(:project) }
context 'with an empty relative URL root' do
before do
allow(Gitlab::Application.routes).to receive(:default_url_options)
.and_return(protocol: 'http', host: 'localhost', script_name: '')
end
it { is_expected.to eql("http://localhost/api/v4/projects/#{user_project.id}/packages/generic") }
end
context 'with a forward slash relative URL root' do
before do
allow(Gitlab::Application.routes).to receive(:default_url_options)
.and_return(protocol: 'http', host: 'localhost', script_name: '/')
end
it { is_expected.to eql("http://localhost/api/v4/projects/#{user_project.id}/packages/generic") }
end
context 'with a relative URL root' do
before do
allow(Gitlab::Application.routes).to receive(:default_url_options)
.and_return(protocol: 'http', host: 'localhost', script_name: '/gitlab/root')
end
it { is_expected.to eql("http://localhost/gitlab/root/api/v4/projects/#{user_project.id}/packages/generic") }
end
end
describe '#candidates_order_params' do
using RSpec::Parameterized::TableSyntax

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe ExtractsRef do
RSpec.describe ExtractsRef, feature_category: :source_code_management do
include described_class
include RepoHelpers
@ -98,6 +98,12 @@ RSpec.describe ExtractsRef do
it { is_expected.to eq(nil) }
end
context 'when ref_type is a hash' do
let(:ref_type) { { 'just' => 'hash' } }
it { is_expected.to eq(nil) }
end
end
it_behaves_like 'extracts refs'

View File

@ -0,0 +1,130 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'find work items by reference', feature_category: :portfolio_management do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
let_it_be(:project) { create(:project, :repository, :public) }
let_it_be(:group2) { create(:group, :public) }
let_it_be(:project2) { create(:project, :repository, :public, group: group2) }
let_it_be(:private_project2) { create(:project, :repository, :private, group: group2) }
let_it_be(:work_item) { create(:work_item, :task, project: project2) }
let_it_be(:private_work_item) { create(:work_item, :task, project: private_project2) }
let(:references) { [work_item.to_reference(full: true), private_work_item.to_reference(full: true)] }
shared_examples 'response with matching work items' do
it 'returns accessible work item' do
post_graphql(query, current_user: current_user)
expected_items = items.map { |item| a_graphql_entity_for(item) }
expect(graphql_data_at('workItemsByReference', 'nodes')).to match(expected_items)
end
end
context 'when user has access only to public work items' do
it_behaves_like 'a working graphql query that returns data' do
before do
post_graphql(query, current_user: current_user)
end
end
it_behaves_like 'response with matching work items' do
let(:items) { [work_item] }
end
it 'avoids N+1 queries', :use_sql_query_cache do
post_graphql(query, current_user: current_user) # warm up
control_count = ActiveRecord::QueryRecorder.new(skip_cached: false) do
post_graphql(query, current_user: current_user)
end
expect(graphql_data_at('workItemsByReference', 'nodes').size).to eq(1)
extra_work_items = create_list(:work_item, 2, :task, project: project2)
refs = references + extra_work_items.map { |item| item.to_reference(full: true) }
expect do
post_graphql(query(refs: refs), current_user: current_user)
end.not_to exceed_all_query_limit(control_count)
expect(graphql_data_at('workItemsByReference', 'nodes').size).to eq(3)
end
end
context 'when user has access to work items in private project' do
before_all do
private_project2.add_guest(current_user)
end
it_behaves_like 'response with matching work items' do
let(:items) { [private_work_item, work_item] }
end
end
context 'when refs includes links' do
let_it_be(:work_item_with_url) { create(:work_item, :task, project: project2) }
let(:references) { [work_item.to_reference(full: true), Gitlab::UrlBuilder.build(work_item_with_url)] }
it_behaves_like 'response with matching work items' do
let(:items) { [work_item_with_url, work_item] }
end
end
context 'when refs includes a short reference present in the context project' do
let_it_be(:same_project_work_item) { create(:work_item, :task, project: project) }
let(:references) { ["##{same_project_work_item.iid}"] }
it_behaves_like 'response with matching work items' do
let(:items) { [same_project_work_item] }
end
end
context 'when user cannot access context namespace' do
it 'returns error' do
post_graphql(query(namespace_path: private_project2.full_path), current_user: current_user)
expect(graphql_data_at('workItemsByReference')).to be_nil
expect(graphql_errors).to contain_exactly(a_hash_including(
'message' => a_string_including("you don't have permission to perform this action"),
'path' => %w[workItemsByReference]
))
end
end
context 'when the context is a group' do
it 'returns empty result' do
group2.add_guest(current_user)
post_graphql(query(namespace_path: group2.full_path), current_user: current_user)
expect_graphql_errors_to_be_empty
expect(graphql_data_at('workItemsByReference', 'nodes')).to be_empty
end
end
context 'when there are more than the max allowed references' do
let(:references_limit) { ::Resolvers::WorkItemReferencesResolver::REFERENCES_LIMIT }
let(:references) { (0..references_limit).map { |n| "##{n}" } }
let(:error_msg) do
"Number of references exceeds the limit. " \
"Please provide no more than #{references_limit} references at the same time."
end
it 'returns an error message' do
post_graphql(query, current_user: current_user)
expect_graphql_errors_to_include(error_msg)
end
end
def query(namespace_path: project.full_path, refs: references)
fields = <<~GRAPHQL
nodes {
#{all_graphql_fields_for('WorkItem', max_depth: 2)}
}
GRAPHQL
graphql_query_for('workItemsByReference', { contextNamespacePath: namespace_path, refs: refs }, fields)
end
end

View File

@ -30,7 +30,8 @@ module Support
duration = time_now - @start_time
elapsed_time = time_now - @rspec_test_suite_start_time
output.puts "\n# Example group #{notification.group.description} took #{readable_duration(duration)}."
output.puts "\n# Example group #{notification.group.description} " \
"(#{notification.group.metadata[:file_path]}) took #{readable_duration(duration)}."
output.puts "# RSpec elapsed time: #{readable_duration(elapsed_time)}.\n\n"
end
end

View File

@ -43,6 +43,7 @@ RSpec.shared_context 'with FOSS query type fields' do
:user,
:users,
:work_item,
:work_items_by_reference,
:audit_event_definitions,
:abuse_report,
:abuse_report_labels

View File

@ -96,18 +96,6 @@ RSpec.shared_examples 'an update storage move worker' do
expect(repository_storage_move.reload).to be_failed
end
end
context 'when feature flag "use_lock_for_update_repository_storage" is disabled' do
before do
stub_feature_flags(use_lock_for_update_repository_storage: false)
end
it 'ignores lock and calls the update repository storage service' do
expect(service).to receive(:execute)
subject
end
end
end
end
end
@ -172,18 +160,6 @@ RSpec.shared_examples 'an update storage move worker' do
expect(repository_storage_move.reload).to be_failed
end
end
context 'when feature flag "use_lock_for_update_repository_storage" is disabled' do
before do
stub_feature_flags(use_lock_for_update_repository_storage: false)
end
it 'ignores lock and calls the update repository storage service' do
expect(service).to receive(:execute)
subject
end
end
end
end
end

View File

@ -148,6 +148,12 @@ RSpec.describe Tooling::ParallelRSpecRunner, feature_category: :tooling do # rub
Parsing expected rspec suite duration...
03_spec.rb not found in master report
RSpec suite is expected to take 1 minute 5 seconds.
Expected duration for tests:
{
"01_spec.rb": 65
}
Running command: bundle exec rspec -- 01_spec.rb 03_spec.rb
MARKDOWN

View File

@ -90,6 +90,9 @@ module Tooling
knapsack_dir = File.dirname(ENV['KNAPSACK_RSPEC_SUITE_REPORT_PATH'])
FileUtils.mkdir_p(knapsack_dir)
File.write(File.join(knapsack_dir, 'node_specs_expected_duration.json'), JSON.dump(expected_duration_report))
Knapsack.logger.info "Expected duration for tests:\n\n"
Knapsack.logger.info "#{JSON.pretty_generate(expected_duration_report)}\n\n"
end
if node_tests.empty?