diff --git a/app/assets/javascripts/ci/job_details/index.js b/app/assets/javascripts/ci/job_details/index.js index dee77d404a8..424ae698861 100644 --- a/app/assets/javascripts/ci/job_details/index.js +++ b/app/assets/javascripts/ci/job_details/index.js @@ -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, }, }); }, diff --git a/app/assets/javascripts/ci/job_details/job_app.vue b/app/assets/javascripts/ci/job_details/job_app.vue index 1c581a2df78..491cd1d35b9 100644 --- a/app/assets/javascripts/ci/job_details/job_app.vue +++ b/app/assets/javascripts/ci/job_details/job_app.vue @@ -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" /> diff --git a/app/graphql/resolvers/work_item_references_resolver.rb b/app/graphql/resolvers/work_item_references_resolver.rb new file mode 100644 index 00000000000..4aa071519db --- /dev/null +++ b/app/graphql/resolvers/work_item_references_resolver.rb @@ -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 diff --git a/app/graphql/types/query_type.rb b/app/graphql/types/query_type.rb index 9c64e056f87..0e39ff2c030 100644 --- a/app/graphql/types/query_type.rb +++ b/app/graphql/types/query_type.rb @@ -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 diff --git a/app/helpers/ci/jobs_helper.rb b/app/helpers/ci/jobs_helper.rb index 5e902ce7bc6..c3af2de16e1 100644 --- a/app/helpers/ci/jobs_helper.rb +++ b/app/helpers/ci/jobs_helper.rb @@ -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) diff --git a/app/workers/concerns/update_repository_storage_worker.rb b/app/workers/concerns/update_repository_storage_worker.rb index ba4998c2102..fd437ebc158 100644 --- a/app/workers/concerns/update_repository_storage_worker.rb +++ b/app/workers/concerns/update_repository_storage_worker.rb @@ -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 diff --git a/config/feature_flags/development/use_lock_for_update_repository_storage.yml b/config/feature_flags/development/use_lock_for_update_repository_storage.yml deleted file mode 100644 index 8f08208bca0..00000000000 --- a/config/feature_flags/development/use_lock_for_update_repository_storage.yml +++ /dev/null @@ -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 diff --git a/db/docs/batched_background_migrations/backfill_code_suggestions_namespace_settings.yml b/db/docs/batched_background_migrations/backfill_code_suggestions_namespace_settings.yml index 23f3fd43174..a6e04394d24 100644 --- a/db/docs/batched_background_migrations/backfill_code_suggestions_namespace_settings.yml +++ b/db/docs/batched_background_migrations/backfill_code_suggestions_namespace_settings.yml @@ -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' diff --git a/db/post_migrate/20231205092529_drop_unique_index_job_id_file_type_to_ci_job_artifact.rb b/db/post_migrate/20231205092529_drop_unique_index_job_id_file_type_to_ci_job_artifact.rb new file mode 100644 index 00000000000..df52f76e1c4 --- /dev/null +++ b/db/post_migrate/20231205092529_drop_unique_index_job_id_file_type_to_ci_job_artifact.rb @@ -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 diff --git a/db/post_migrate/20231206145850_finalize_backfill_code_suggestions_namespace_settings.rb b/db/post_migrate/20231206145850_finalize_backfill_code_suggestions_namespace_settings.rb new file mode 100644 index 00000000000..3aa30ef2bb6 --- /dev/null +++ b/db/post_migrate/20231206145850_finalize_backfill_code_suggestions_namespace_settings.rb @@ -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 diff --git a/db/schema_migrations/20231205092529 b/db/schema_migrations/20231205092529 new file mode 100644 index 00000000000..adc729e9f7a --- /dev/null +++ b/db/schema_migrations/20231205092529 @@ -0,0 +1 @@ +5ca9bd14a7c69b4b77745303c47c7d11890f3ced97c8a1a68b5b713b29a2dab7 \ No newline at end of file diff --git a/db/schema_migrations/20231206145850 b/db/schema_migrations/20231206145850 new file mode 100644 index 00000000000..6d291813c04 --- /dev/null +++ b/db/schema_migrations/20231206145850 @@ -0,0 +1 @@ +53442f9c3ef0e0f3f31b4be177faf3d073ee8b74d20ede7a1673bedfa097f0b9 \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 551de511faf..b8b0f57a22e 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -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); diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index a8dc50834d4..757bc559c29 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -1033,6 +1033,27 @@ Returns [`WorkItem`](#workitem). | ---- | ---- | ----------- | | `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 | +| ---- | ---- | ----------- | +| `contextNamespacePath` | [`ID`](#id) | Full path of the context namespace (project or group). | +| `refs` | [`[String!]!`](#string) | Work item references. Can be either a short reference or URL. | + ### `Query.workspace` Find a workspace. diff --git a/doc/user/project/repository/code_suggestions/index.md b/doc/user/project/repository/code_suggestions/index.md index f7a8d9cac8f..d73668c632c 100644 --- a/doc/user/project/repository/code_suggestions/index.md +++ b/doc/user/project/repository/code_suggestions/index.md @@ -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.
View how to setup and use GitLab Duo Code Suggestions. @@ -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 Tab. -1. To ignore a suggestion, keep typing as you usually would. -1. To explicitly reject a suggestion, press esc. - -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). diff --git a/doc/user/project/repository/code_suggestions/saas.md b/doc/user/project/repository/code_suggestions/saas.md index 77a364f14ff..9abafa72616 100644 --- a/doc/user/project/repository/code_suggestions/saas.md +++ b/doc/user/project/repository/code_suggestions/saas.md @@ -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 Tab. +1. To ignore a suggestion, keep typing as you usually would. +1. To explicitly reject a suggestion, press Esc. + +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. diff --git a/doc/user/project/repository/code_suggestions/self_managed.md b/doc/user/project/repository/code_suggestions/self_managed.md index 2e06191a002..6d05aacca54 100644 --- a/doc/user/project/repository/code_suggestions/self_managed.md +++ b/doc/user/project/repository/code_suggestions/self_managed.md @@ -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 Tab. +1. To ignore a suggestion, keep typing as you usually would. +1. To explicitly reject a suggestion, press Esc. + +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 diff --git a/lib/api/entities/ml/mlflow/model_version.rb b/lib/api/entities/ml/mlflow/model_version.rb index 2f4851d6d58..10fdf3822a5 100644 --- a/lib/api/entities/ml/mlflow/model_version.rb +++ b/lib/api/entities/ml/mlflow/model_version.rb @@ -57,7 +57,7 @@ module API end def run_id - "" + object.candidate.eid end def status diff --git a/lib/api/entities/ml/mlflow/run_info.rb b/lib/api/entities/ml/mlflow/run_info.rb index 6133b3a9d4b..04c6069617c 100644 --- a/lib/api/entities/ml/mlflow/run_info.rb +++ b/lib/api/entities/ml/mlflow/run_info.rb @@ -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 diff --git a/lib/api/ml/mlflow/api_helpers.rb b/lib/api/ml/mlflow/api_helpers.rb index fb830dfe9e3..66d79753110 100644 --- a/lib/api/ml/mlflow/api_helpers.rb +++ b/lib/api/ml/mlflow/api_helpers.rb @@ -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 diff --git a/lib/api/ml/mlflow/runs.rb b/lib/api/ml/mlflow/runs.rb index 6716db21407..d03bf1fc576 100644 --- a/lib/api/ml/mlflow/runs.rb +++ b/lib/api/ml/mlflow/runs.rb @@ -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 diff --git a/lib/extracts_ref/ref_extractor.rb b/lib/extracts_ref/ref_extractor.rb index ac9b0ebb7af..e716c64e63a 100644 --- a/lib/extracts_ref/ref_extractor.rb +++ b/lib/extracts_ref/ref_extractor.rb @@ -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 diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 3254a800c53..af4930a341b 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -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 "" diff --git a/rubocop/cop/graphql/id_type.rb b/rubocop/cop/graphql/id_type.rb index 53d79751fa8..deed68da0b3 100644 --- a/rubocop/cop/graphql/id_type.rb +++ b/rubocop/cop/graphql/id_type.rb @@ -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) ...) diff --git a/spec/helpers/ci/jobs_helper_spec.rb b/spec/helpers/ci/jobs_helper_spec.rb index f877e0d87b3..315bda9b2c1 100644 --- a/spec/helpers/ci/jobs_helper_spec.rb +++ b/spec/helpers/ci/jobs_helper_spec.rb @@ -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" diff --git a/spec/lib/api/entities/ml/mlflow/run_info_spec.rb b/spec/lib/api/entities/ml/mlflow/run_info_spec.rb index 1664d9f18d2..f631a9cb803 100644 --- a/spec/lib/api/entities/ml/mlflow/run_info_spec.rb +++ b/spec/lib/api/entities/ml/mlflow/run_info_spec.rb @@ -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 diff --git a/spec/lib/api/ml/mlflow/api_helpers_spec.rb b/spec/lib/api/ml/mlflow/api_helpers_spec.rb index 1f2490fe5eb..3e7a0187d86 100644 --- a/spec/lib/api/ml/mlflow/api_helpers_spec.rb +++ b/spec/lib/api/ml/mlflow/api_helpers_spec.rb @@ -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 diff --git a/spec/lib/extracts_ref_spec.rb b/spec/lib/extracts_ref_spec.rb index 9ff11899e89..c7186011654 100644 --- a/spec/lib/extracts_ref_spec.rb +++ b/spec/lib/extracts_ref_spec.rb @@ -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' diff --git a/spec/requests/api/graphql/work_items_by_reference_spec.rb b/spec/requests/api/graphql/work_items_by_reference_spec.rb new file mode 100644 index 00000000000..ad2303a81e7 --- /dev/null +++ b/spec/requests/api/graphql/work_items_by_reference_spec.rb @@ -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 diff --git a/spec/support/rspec_run_time.rb b/spec/support/rspec_run_time.rb index 71e2178f4db..875bdc0852e 100644 --- a/spec/support/rspec_run_time.rb +++ b/spec/support/rspec_run_time.rb @@ -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 diff --git a/spec/support/shared_contexts/graphql/types/query_type_shared_context.rb b/spec/support/shared_contexts/graphql/types/query_type_shared_context.rb index 257ccc553fe..6ab41d87f44 100644 --- a/spec/support/shared_contexts/graphql/types/query_type_shared_context.rb +++ b/spec/support/shared_contexts/graphql/types/query_type_shared_context.rb @@ -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 diff --git a/spec/support/shared_examples/workers/update_repository_move_shared_examples.rb b/spec/support/shared_examples/workers/update_repository_move_shared_examples.rb index e5a3975c0a4..cb3cd81f5ce 100644 --- a/spec/support/shared_examples/workers/update_repository_move_shared_examples.rb +++ b/spec/support/shared_examples/workers/update_repository_move_shared_examples.rb @@ -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 diff --git a/spec/tooling/lib/tooling/parallel_rspec_runner_spec.rb b/spec/tooling/lib/tooling/parallel_rspec_runner_spec.rb index 62de742e125..27869974b39 100644 --- a/spec/tooling/lib/tooling/parallel_rspec_runner_spec.rb +++ b/spec/tooling/lib/tooling/parallel_rspec_runner_spec.rb @@ -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 diff --git a/tooling/lib/tooling/parallel_rspec_runner.rb b/tooling/lib/tooling/parallel_rspec_runner.rb index 22d84603318..ba4bdb9df41 100644 --- a/tooling/lib/tooling/parallel_rspec_runner.rb +++ b/tooling/lib/tooling/parallel_rspec_runner.rb @@ -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?