Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
8b6c0125cb
commit
1efcb3739b
|
|
@ -51,9 +51,9 @@ workflow:
|
|||
- source scripts/utils.sh
|
||||
- source scripts/rspec_helpers.sh
|
||||
- source scripts/qa/cng_deploy/cng-kind.sh
|
||||
- run_timed_command "setup_cluster scripts/qa/cng_deploy/config/kind-config.yml"
|
||||
- run_timed_command "deploy ${GITLAB_DOMAIN}"
|
||||
- cd qa && bundle install
|
||||
- bundle exec cng create cluster --ci
|
||||
- deploy ${GITLAB_DOMAIN}
|
||||
script:
|
||||
- export QA_COMMAND="bundle exec bin/qa ${QA_SCENARIO:=Test::Instance::All} $QA_GITLAB_URL -- --force-color --order random --format documentation"
|
||||
- echo "Running - '$QA_COMMAND'"
|
||||
|
|
|
|||
|
|
@ -1939,7 +1939,6 @@ RSpec/ContextWording:
|
|||
- 'spec/lib/gitlab/usage_data_counters/ipynb_diff_activity_counter_spec.rb'
|
||||
- 'spec/lib/gitlab/usage_data_counters/kubernetes_agent_counter_spec.rb'
|
||||
- 'spec/lib/gitlab/usage_data_counters/quick_action_activity_unique_counter_spec.rb'
|
||||
- 'spec/lib/gitlab/usage_data_counters/search_counter_spec.rb'
|
||||
- 'spec/lib/gitlab/usage_data_spec.rb'
|
||||
- 'spec/lib/gitlab/utils/lazy_attributes_spec.rb'
|
||||
- 'spec/lib/gitlab/utils/markdown_spec.rb'
|
||||
|
|
|
|||
|
|
@ -3974,7 +3974,6 @@ RSpec/FeatureCategory:
|
|||
- 'spec/lib/gitlab/usage_data_counters/package_event_counter_spec.rb'
|
||||
- 'spec/lib/gitlab/usage_data_counters/quick_action_activity_unique_counter_spec.rb'
|
||||
- 'spec/lib/gitlab/usage_data_counters/redis_counter_spec.rb'
|
||||
- 'spec/lib/gitlab/usage_data_counters/search_counter_spec.rb'
|
||||
- 'spec/lib/gitlab/usage_data_counters/source_code_counter_spec.rb'
|
||||
- 'spec/lib/gitlab/usage_data_counters/vscode_extension_activity_unique_counter_spec.rb'
|
||||
- 'spec/lib/gitlab/usage_data_counters/web_ide_counter_spec.rb'
|
||||
|
|
|
|||
|
|
@ -219,7 +219,6 @@ Search/NamespacedClass:
|
|||
- 'lib/gitlab/slash_commands/issue_search.rb'
|
||||
- 'lib/gitlab/slash_commands/presenters/issue_search.rb'
|
||||
- 'lib/gitlab/snippet_search_results.rb'
|
||||
- 'lib/gitlab/usage_data_counters/search_counter.rb'
|
||||
- 'lib/peek/views/elasticsearch.rb'
|
||||
- 'lib/peek/views/zoekt.rb'
|
||||
- 'qa/qa/ee/page/admin/settings/component/elasticsearch.rb'
|
||||
|
|
|
|||
|
|
@ -49,6 +49,23 @@ if (process.env.NODE_ENV !== 'production' && gon?.test_env) {
|
|||
import(/* webpackMode: "eager" */ './test_utils');
|
||||
}
|
||||
|
||||
if (gon?.user_color_mode === 'gl-system') {
|
||||
const root = document.documentElement;
|
||||
// eslint-disable-next-line @gitlab/require-i18n-strings
|
||||
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
||||
root.classList.add('gl-dark');
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @gitlab/require-i18n-strings
|
||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
|
||||
if (e.matches) {
|
||||
root.classList.add('gl-dark');
|
||||
} else {
|
||||
root.classList.remove('gl-dark');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('beforeunload', () => {
|
||||
// Unbind scroll events
|
||||
// eslint-disable-next-line @gitlab/no-global-event-off
|
||||
|
|
|
|||
|
|
@ -16,35 +16,6 @@ export const defaultOrganization = {
|
|||
avatar_url: null,
|
||||
};
|
||||
|
||||
export const organizations = [
|
||||
{
|
||||
id: 'gid://gitlab/Organizations::Organization/1',
|
||||
name: 'My First Organization',
|
||||
descriptionHtml:
|
||||
'<p>This is where an organization can be explained in <strong>detail</strong></p>',
|
||||
avatarUrl: 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61',
|
||||
webUrl: '/-/organizations/default',
|
||||
__typename: 'Organization',
|
||||
},
|
||||
{
|
||||
id: 'gid://gitlab/Organizations::Organization/2',
|
||||
name: 'Vegetation Co.',
|
||||
descriptionHtml:
|
||||
'<p> Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet Lorem ipsum dolt Lorem ipsum dolor sit amet Lorem ipsum dolt Lorem ipsum dolor sit amet Lorem ipsum dolt Lorem ipsum dolor sit amet Lorem ipsum dolt Lorem ipsum dolor sit amet Lorem ipsum dolt Lorem ipsum dolor sit amet Lorem ipsum dolt Lorem ipsum dolor sit amet Lorem ipsum dolt Lorem ipsum dolor sit amet Lorem ipsum dolt Lorem ipsum dolor sit amet Lorem ipsum dolt<script>alert(1)</script></p>',
|
||||
avatarUrl: null,
|
||||
webUrl: '/-/organizations/default',
|
||||
__typename: 'Organization',
|
||||
},
|
||||
{
|
||||
id: 'gid://gitlab/Organizations::Organization/3',
|
||||
name: 'Dude where is my car?',
|
||||
descriptionHtml: null,
|
||||
avatarUrl: null,
|
||||
webUrl: '/-/organizations/default',
|
||||
__typename: 'Organization',
|
||||
},
|
||||
];
|
||||
|
||||
export const organizationCreateResponse = {
|
||||
data: {
|
||||
organizationCreate: {
|
||||
|
|
@ -87,27 +58,3 @@ export const organizationUpdateResponseWithErrors = {
|
|||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const pageInfo = {
|
||||
endCursor: 'eyJpZCI6IjEwNTMifQ',
|
||||
hasNextPage: true,
|
||||
hasPreviousPage: true,
|
||||
startCursor: 'eyJpZCI6IjEwNzIifQ',
|
||||
__typename: 'PageInfo',
|
||||
};
|
||||
|
||||
export const pageInfoOnePage = {
|
||||
endCursor: 'eyJpZCI6IjEwNTMifQ',
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
startCursor: 'eyJpZCI6IjEwNzIifQ',
|
||||
__typename: 'PageInfo',
|
||||
};
|
||||
|
||||
export const pageInfoEmpty = {
|
||||
endCursor: null,
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
startCursor: null,
|
||||
__typename: 'PageInfo',
|
||||
};
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ export default {
|
|||
|
||||
return {
|
||||
isSubmitEnabled: true,
|
||||
darkModeOnCreate: null,
|
||||
colorModeOnCreate: null,
|
||||
schemeOnCreate: null,
|
||||
integrationValues,
|
||||
};
|
||||
|
|
@ -72,7 +72,7 @@ export default {
|
|||
this.formEl.addEventListener('ajax:beforeSend', this.handleLoading);
|
||||
this.formEl.addEventListener('ajax:success', this.handleSuccess);
|
||||
this.formEl.addEventListener('ajax:error', this.handleError);
|
||||
this.darkModeOnCreate = this.darkModeSelected();
|
||||
this.colorModeOnCreate = this.getSelectedColorMode();
|
||||
this.schemeOnCreate = this.getSelectedScheme();
|
||||
},
|
||||
beforeDestroy() {
|
||||
|
|
@ -81,10 +81,6 @@ export default {
|
|||
this.formEl.removeEventListener('ajax:error', this.handleError);
|
||||
},
|
||||
methods: {
|
||||
darkModeSelected() {
|
||||
const mode = this.getSelectedColorMode();
|
||||
return mode ? mode.css_class === 'gl-dark' : null;
|
||||
},
|
||||
getSelectedColorMode() {
|
||||
const modeId = new FormData(this.formEl).get('user[color_mode_id]');
|
||||
const mode = this.colorModes.find((item) => item.id === Number(modeId));
|
||||
|
|
@ -105,7 +101,7 @@ export default {
|
|||
// Reload the page if the theme has changed from light to dark mode or vice versa
|
||||
// or if color scheme has changed to correctly load all required styles.
|
||||
if (
|
||||
this.darkModeOnCreate !== this.darkModeSelected() ||
|
||||
this.colorModeOnCreate !== this.getSelectedColorMode() ||
|
||||
this.schemeOnCreate !== this.getSelectedScheme()
|
||||
) {
|
||||
window.location.reload();
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ export default {
|
|||
<gl-form-textarea
|
||||
:id="inputId"
|
||||
v-model="messageText"
|
||||
class="form-control js-gfm-input gl-mb-3 commit-message-edit"
|
||||
class="form-control js-gfm-input gl-mb-3 commit-message-edit !gl-font-monospace"
|
||||
dir="auto"
|
||||
required="required"
|
||||
rows="7"
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ class SearchController < ApplicationController
|
|||
include ControllerWithCrossProjectAccessCheck
|
||||
include SearchHelper
|
||||
include ProductAnalyticsTracking
|
||||
include Gitlab::InternalEventsTracking
|
||||
include SearchRateLimitable
|
||||
|
||||
RESCUE_FROM_TIMEOUT_ACTIONS = [:count, :show, :autocomplete, :aggregations].freeze
|
||||
|
|
@ -187,11 +188,11 @@ class SearchController < ApplicationController
|
|||
end
|
||||
|
||||
def increment_search_counters
|
||||
Gitlab::UsageDataCounters::SearchCounter.count(:all_searches)
|
||||
track_internal_event('perform_search', user: current_user)
|
||||
|
||||
return if params[:nav_source] != 'navbar'
|
||||
|
||||
Gitlab::UsageDataCounters::SearchCounter.count(:navbar_searches)
|
||||
track_internal_event('perform_navbar_search', user: current_user)
|
||||
end
|
||||
|
||||
def append_info_to_payload(payload)
|
||||
|
|
|
|||
|
|
@ -79,6 +79,10 @@ module PreferencesHelper
|
|||
user_application_color_mode == 'gl-dark'
|
||||
end
|
||||
|
||||
def user_application_system_mode?
|
||||
user_application_color_mode == 'gl-system'
|
||||
end
|
||||
|
||||
def user_theme_primary_color
|
||||
Gitlab::Themes.for_user(current_user).primary_color
|
||||
end
|
||||
|
|
|
|||
|
|
@ -286,12 +286,6 @@ module Ci
|
|||
end
|
||||
end
|
||||
|
||||
def expand_file_refs?
|
||||
strong_memoize(:expand_file_refs) do
|
||||
!Feature.enabled?(:ci_prevent_file_var_expansion_downstream_pipeline, project)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def expose_protected_group_variables?
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ module Packages
|
|||
inverse_of: :component,
|
||||
dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
|
||||
|
||||
delegate container_type, to: :distribution
|
||||
|
||||
validates :distribution,
|
||||
presence: true
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ module Packages
|
|||
belongs_to :component, class_name: "Packages::Debian::#{container_type.capitalize}Component", inverse_of: :files
|
||||
belongs_to :architecture, class_name: "Packages::Debian::#{container_type.capitalize}Architecture", inverse_of: :files, optional: true
|
||||
|
||||
delegate container_type, to: :component
|
||||
|
||||
enum file_type: { packages: 1, sources: 2, di_packages: 3 }
|
||||
enum compression_type: { gz: 1, bz2: 2, xz: 3 }
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ module Packages
|
|||
|
||||
belongs_to :package, -> { where(package_type: :nuget) }, inverse_of: :nuget_symbols
|
||||
|
||||
delegate :project_id, to: :package
|
||||
delegate :project_id, :project, to: :package
|
||||
|
||||
validates :package, :file, :file_path, :signature, :object_storage_key, :size, presence: true
|
||||
validates :signature, uniqueness: { scope: :file_path }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
class Packages::Composer::CacheUploader < GitlabUploader
|
||||
include ObjectStorage::Concern
|
||||
include Packages::GcsSignedUrlMetadata
|
||||
|
||||
storage_location :packages
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
class Packages::Debian::ComponentFileUploader < GitlabUploader
|
||||
extend Workhorse::UploadPath
|
||||
include ObjectStorage::Concern
|
||||
include Packages::GcsSignedUrlMetadata
|
||||
|
||||
storage_location :packages
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
class Packages::Debian::DistributionReleaseFileUploader < GitlabUploader
|
||||
extend Workhorse::UploadPath
|
||||
include ObjectStorage::Concern
|
||||
include Packages::GcsSignedUrlMetadata
|
||||
|
||||
storage_location :packages
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# This module is used to augment the GCS signed URL with metadata that is used for auditing purposes.
|
||||
# The uploader that includes this module should respond to `group` or `project` methods.
|
||||
# If the underlying model instance does not have the `size` stored in the DB, we will make a stats request
|
||||
# to fetch the file size from the object storage.
|
||||
module Packages
|
||||
module GcsSignedUrlMetadata
|
||||
def url(*args, **kwargs)
|
||||
return super if ::Feature.disabled?(:augment_gcs_signed_url_with_metadata, Feature.current_request)
|
||||
return super unless fog_credentials[:provider] == 'Google' && Gitlab.com? # rubocop:disable Gitlab/AvoidGitlabInstanceChecks -- As per https://gitlab.com/groups/gitlab-org/-/epics/8834, we are only interested in egress traffic on Gitlab.com
|
||||
|
||||
project = model.try(:project)
|
||||
root_namespace = project&.root_namespace || model.try(:group)&.root_ancestor
|
||||
|
||||
metadata_params = {
|
||||
'x-goog-custom-audit-gitlab-project' => project&.id,
|
||||
'x-goog-custom-audit-gitlab-namespace' => root_namespace&.id,
|
||||
'x-goog-custom-audit-gitlab-size-bytes' => model.try(:size) || size
|
||||
}.compact.transform_values(&:to_s)
|
||||
|
||||
super(*args, **kwargs.merge(query: metadata_params))
|
||||
rescue StandardError => e
|
||||
Gitlab::ErrorTracking.track_exception(
|
||||
e,
|
||||
model_class: model.class.name,
|
||||
model_id: model.id
|
||||
)
|
||||
super
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -4,6 +4,7 @@ module Packages
|
|||
module Npm
|
||||
class MetadataCacheUploader < GitlabUploader
|
||||
include ObjectStorage::Concern
|
||||
include Packages::GcsSignedUrlMetadata
|
||||
|
||||
FILENAME = 'metadata.json'
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ module Packages
|
|||
module Nuget
|
||||
class SymbolUploader < GitlabUploader
|
||||
include ObjectStorage::Concern
|
||||
include Packages::GcsSignedUrlMetadata
|
||||
|
||||
storage_location :packages
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
class Packages::PackageFileUploader < GitlabUploader
|
||||
extend Workhorse::UploadPath
|
||||
include ObjectStorage::Concern
|
||||
include Packages::GcsSignedUrlMetadata
|
||||
|
||||
storage_location :packages
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ module Packages
|
|||
module Rpm
|
||||
class RepositoryFileUploader < GitlabUploader
|
||||
include ObjectStorage::Concern
|
||||
include Packages::GcsSignedUrlMetadata
|
||||
|
||||
storage_location :packages
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,13 @@
|
|||
= stylesheet_link_tag_defer "application_dark"
|
||||
= yield :page_specific_styles
|
||||
= stylesheet_link_tag_defer "application_utilities_dark"
|
||||
- elsif user_application_system_mode?
|
||||
%meta{ name: 'color-scheme', content: 'light dark' }
|
||||
= stylesheet_link_tag "application", media: "(prefers-color-scheme: light)"
|
||||
= stylesheet_link_tag "application_dark", media: "(prefers-color-scheme: dark)"
|
||||
= yield :page_specific_styles
|
||||
= stylesheet_link_tag "application_utilities", media: "(prefers-color-scheme: light)"
|
||||
= stylesheet_link_tag "application_utilities_dark", media: "(prefers-color-scheme: dark)"
|
||||
- else
|
||||
= stylesheet_link_tag_defer "application"
|
||||
= yield :page_specific_styles
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
---
|
||||
description: Basic Search and Advanced Search requests using the navbar.
|
||||
internal_events: true
|
||||
action: perform_navbar_search
|
||||
identifiers:
|
||||
- user
|
||||
product_section: core_platform
|
||||
product_stage: data_stores
|
||||
product_group: global_search
|
||||
milestone: '17.0'
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/151599
|
||||
distributions:
|
||||
- ce
|
||||
- ee
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
---
|
||||
description: All Basic Search and Advanced Search requests.
|
||||
internal_events: true
|
||||
action: perform_search
|
||||
identifiers:
|
||||
- user
|
||||
product_section: core_platform
|
||||
product_stage: data_stores
|
||||
product_group: global_search
|
||||
milestone: '17.0'
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/151599
|
||||
distributions:
|
||||
- ce
|
||||
- ee
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
name: ci_prevent_file_var_expansion_downstream_pipeline
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/124320
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/414583
|
||||
milestone: '16.3'
|
||||
type: development
|
||||
group: group::pipeline security
|
||||
default_enabled: false
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
name: augment_gcs_signed_url_with_metadata
|
||||
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/443335
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/147207
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/458729
|
||||
milestone: '17.0'
|
||||
group: group::package registry
|
||||
type: gitlab_com_derisk
|
||||
default_enabled: false
|
||||
|
|
@ -9,12 +9,9 @@ product_group: global_search
|
|||
value_type: number
|
||||
status: active
|
||||
time_frame: all
|
||||
data_source: redis
|
||||
instrumentation_class: RedisMetric
|
||||
options:
|
||||
prefix: null
|
||||
event: all_searches_count
|
||||
include_usage_prefix: false
|
||||
data_source: internal_events
|
||||
events:
|
||||
- name: perform_search
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
|
|
|
|||
|
|
@ -9,12 +9,9 @@ product_group: global_search
|
|||
value_type: number
|
||||
status: active
|
||||
time_frame: all
|
||||
data_source: redis
|
||||
instrumentation_class: RedisMetric
|
||||
options:
|
||||
prefix: null
|
||||
event: navbar_searches_count
|
||||
include_usage_prefix: false
|
||||
data_source: internal_events
|
||||
events:
|
||||
- name: perform_navbar_search
|
||||
distribution:
|
||||
- ce
|
||||
- ee
|
||||
|
|
|
|||
|
|
@ -533,6 +533,8 @@ ldapsearch
|
|||
Lefthook
|
||||
Leiningen
|
||||
Lemmy
|
||||
LLM
|
||||
LLMs
|
||||
libFuzzer
|
||||
Libgcrypt
|
||||
Libravatar
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ Prerequisites:
|
|||
#### Configure network and proxy settings
|
||||
|
||||
For self-managed instances, to enable GitLab Duo features,
|
||||
You must [enable network connectivity](../user/ai_features_enable.md#enable-gitlab-duo-on-self-managed-instances).
|
||||
You must [enable network connectivity](../user/ai_features_enable.md#configure-gitlab-duo-on-a-self-managed-instance).
|
||||
|
||||
## Assign and remove seats in bulk
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,76 @@
|
|||
---
|
||||
stage: AI-powered
|
||||
group: AI Model Validation
|
||||
description: AI-powered features and functionality.
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
# GitLab Duo data usage
|
||||
|
||||
GitLab Duo uses generative AI to help increase your velocity and make you more productive. Each AI-powered feature operates independently and is not required for other features to function.
|
||||
|
||||
GitLab uses best-in-class large language models (LLMs) for specific tasks. These LLMs are
|
||||
[Google Vertex AI Models](https://cloud.google.com/vertex-ai/generative-ai/docs/learn/overview#genai-models)
|
||||
and [Anthropic Claude](https://www.anthropic.com/product).
|
||||
|
||||
## Progressive enhancement
|
||||
|
||||
GitLab Duo AI-powered features are designed as a progressive enhancement to existing GitLab features across the DevSecOps platform. These features are designed to fail gracefully and should not prevent the core functionality of the underlying feature. You should note each feature is subject to its expected functionality as defined by the relevant [feature support policy](../policy/experiment-beta-support.md).
|
||||
|
||||
## Stability and performance
|
||||
|
||||
GitLab Duo AI-powered features are in a variety of [feature support levels](../policy/experiment-beta-support.md#beta). Due to the nature of these features, there may be high demand for usage which may cause degraded performance or unexpected downtime of the feature. We have built these features to gracefully degrade and have controls in place to allow us to mitigate abuse or misuse. GitLab may disable Beta and Experiment features for any or all customers at any time at our discretion.
|
||||
|
||||
## Data privacy
|
||||
|
||||
GitLab Duo AI-powered features are powered by a generative AI model. The processing of any personal data is in accordance with our [Privacy Statement](https://about.gitlab.com/privacy/). You may also visit the [Sub-Processors page](https://about.gitlab.com/privacy/subprocessors/#third-party-sub-processors) to see the list of Sub-Processors we use to provide these features.
|
||||
|
||||
## Data retention
|
||||
|
||||
The below reflects the current retention periods of GitLab AI model [Sub-Processors](https://about.gitlab.com/privacy/subprocessors/#third-party-sub-processors):
|
||||
|
||||
- Anthropic discards model input and output data immediately after the output is provided. Anthropic currently does not store data for abuse monitoring. Model input and output is not used to train models.
|
||||
- Google discards model input and output data immediately after the output is provided. Google currently does not store data for abuse monitoring. Model input and output is not used to train models.
|
||||
|
||||
All of these AI providers are under data protection agreements with GitLab that prohibit the use of Customer Content for their own purposes, except to perform their independent legal obligations.
|
||||
|
||||
GitLab retains input and output for up to 30 days for the purpose of troubleshooting, debugging, and addressing latency issues.
|
||||
|
||||
## Training data
|
||||
|
||||
GitLab does not train generative AI models based on private (non-public) data. The vendors we work with also do not train models based on private data.
|
||||
|
||||
For more information on our AI [sub-processors](https://about.gitlab.com/privacy/subprocessors/#third-party-sub-processors), see:
|
||||
|
||||
- Google Vertex AI Models APIs [data governance](https://cloud.google.com/vertex-ai/generative-ai/docs/data-governance), [responsible AI](https://cloud.google.com/vertex-ai/generative-ai/docs/learn/responsible-ai), and [release notes](https://cloud.google.com/vertex-ai/docs/release-notes).
|
||||
- Anthropic Claude's [constitution](https://www.anthropic.com/news/claudes-constitution), training data [FAQ](https://support.anthropic.com/en/articles/7996885-how-do-you-use-personal-data-in-model-training), and [data recency article](https://support.anthropic.com/en/articles/8114494-how-up-to-date-is-claude-s-training-data).
|
||||
|
||||
## Telemetry
|
||||
|
||||
GitLab Duo collects aggregated or de-identified first-party usage data through a [Snowplow collector](https://handbook.gitlab.com/handbook/business-technology/data-team/platform/snowplow/). This usage data includes the following metrics:
|
||||
|
||||
- Number of unique users
|
||||
- Number of unique instances
|
||||
- Prompt and suffix lengths
|
||||
- Model used
|
||||
- Status code responses
|
||||
- API responses times
|
||||
- Code Suggestions also collects:
|
||||
- Language the suggestion was in (for example, Python)
|
||||
- Editor being used (for example, VS Code)
|
||||
- Number of suggestions shown, accepted, rejected, or that had errors
|
||||
- Duration of time that a suggestion was shown
|
||||
|
||||
## Model accuracy and quality
|
||||
|
||||
Generative AI may produce unexpected results that may be:
|
||||
|
||||
- Low-quality
|
||||
- Incoherent
|
||||
- Incomplete
|
||||
- Produce failed pipelines
|
||||
- Insecure code
|
||||
- Offensive or insensitive
|
||||
- Out of date information
|
||||
|
||||
GitLab is actively iterating on all our AI-assisted capabilities to improve the quality of the generated content. We improve the quality through prompt engineering, evaluating new AI/ML models to power these features, and through novel heuristics built into these features directly.
|
||||
|
|
@ -62,64 +62,3 @@ Disable GitLab Duo features by [following these instructions](ai_features_enable
|
|||
| [Root cause analysis](ai_experiments.md#root-cause-analysis) | Vertex AI Codey [`text-bison`](https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/text) |
|
||||
| [Value stream forecasting](ai_experiments.md#forecast-deployment-frequency-with-value-stream-forecasting) | Statistical forecasting |
|
||||
| [Product analytics](analytics/analytics_dashboards.md#generate-a-custom-visualization-with-gitlab-duo) | Vertex AI Codey [`codechat-bison`](https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/code-chat) |
|
||||
|
||||
## Data usage
|
||||
|
||||
GitLab AI features leverage generative AI to help increase velocity and aim to help make you more productive. Each feature operates independently of other features and is not required for other features to function. GitLab selects the best-in-class large-language models for specific tasks. We use [Google Vertex AI Models](https://cloud.google.com/vertex-ai/generative-ai/docs/learn/overview#genai-models) and [Anthropic Claude](https://www.anthropic.com/product).
|
||||
|
||||
### Progressive enhancement
|
||||
|
||||
These features are designed as a progressive enhancement to existing GitLab features across our DevSecOps platform. They are designed to fail gracefully and should not prevent the core functionality of the underlying feature. You should note each feature is subject to its expected functionality as defined by the relevant [feature support policy](../policy/experiment-beta-support.md).
|
||||
|
||||
### Stability and performance
|
||||
|
||||
These features are in a variety of [feature support levels](../policy/experiment-beta-support.md#beta). Due to the nature of these features, there may be high demand for usage which may cause degraded performance or unexpected downtime of the feature. We have built these features to gracefully degrade and have controls in place to allow us to mitigate abuse or misuse. GitLab may disable **beta and experimental** features for any or all customers at any time at our discretion.
|
||||
|
||||
### Data privacy
|
||||
|
||||
GitLab Duo AI features are powered by a generative AI models. The processing of any personal data is in accordance with our [Privacy Statement](https://about.gitlab.com/privacy/). You may also visit the [Sub-Processors page](https://about.gitlab.com/privacy/subprocessors/#third-party-sub-processors) to see the list of our Sub-Processors that we use to provide these features.
|
||||
|
||||
### Data retention
|
||||
|
||||
The below reflects the current retention periods of GitLab AI model [Sub-Processors](https://about.gitlab.com/privacy/subprocessors/#third-party-sub-processors):
|
||||
|
||||
- Anthropic discards model input and output data immediately after the output is provided. Anthropic currently does not store data for abuse monitoring. Model input and output is not used to train models.
|
||||
- Google discards model input and output data immediately after the output is provided. Google currently does not store data for abuse monitoring. Model input and output is not used to train models.
|
||||
|
||||
All of these AI providers are under data protection agreements with GitLab that prohibit the use of Customer Content for their own purposes, except to perform their independent legal obligations.
|
||||
|
||||
GitLab retains input and output for up to 30 days for the purpose of troubleshooting, debugging, and addressing latency issues.
|
||||
|
||||
### Training data
|
||||
|
||||
GitLab does not train generative AI models based on private (non-public) data. The vendors we work with also do not train models based on private data.
|
||||
|
||||
For more information on our AI [sub-processors](https://about.gitlab.com/privacy/subprocessors/#third-party-sub-processors), see:
|
||||
|
||||
- Google Vertex AI Models APIs [data governance](https://cloud.google.com/vertex-ai/generative-ai/docs/data-governance), [responsible AI](https://cloud.google.com/vertex-ai/generative-ai/docs/learn/responsible-ai), and [release notes](https://cloud.google.com/vertex-ai/docs/release-notes).
|
||||
- Anthropic Claude's [constitution](https://www.anthropic.com/news/claudes-constitution), training data [FAQ](https://support.anthropic.com/en/articles/7996885-how-do-you-use-personal-data-in-model-training), and [data recency article](https://support.anthropic.com/en/articles/8114494-how-up-to-date-is-claude-s-training-data).
|
||||
|
||||
### Telemetry
|
||||
|
||||
GitLab Duo collects aggregated or de-identified first-party usage data through our [Snowplow collector](https://handbook.gitlab.com/handbook/business-technology/data-team/platform/snowplow/). This usage data includes the following metrics:
|
||||
|
||||
- Number of unique users
|
||||
- Number of unique instances
|
||||
- Prompt lengths
|
||||
- Model used
|
||||
- Status code responses
|
||||
- API responses times
|
||||
|
||||
### Model accuracy and quality
|
||||
|
||||
Generative AI may produce unexpected results that may be:
|
||||
|
||||
- Low-quality
|
||||
- Incoherent
|
||||
- Incomplete
|
||||
- Produce failed pipelines
|
||||
- Insecure code
|
||||
- Offensive or insensitive
|
||||
- Out of date information
|
||||
|
||||
GitLab is actively iterating on all our AI-assisted capabilities to improve the quality of the generated content. We improve the quality through prompt engineering, evaluating new AI/ML models to power these features, and through novel heuristics built into these features directly.
|
||||
|
|
|
|||
|
|
@ -10,33 +10,33 @@ DETAILS:
|
|||
**Tier:** Premium, Ultimate
|
||||
**Offering:** GitLab.com, Self-managed, GitLab Dedicated
|
||||
|
||||
> - [Settings to disable AI features introduced](https://gitlab.com/groups/gitlab-org/-/epics/12404) in GitLab 16.10.
|
||||
> - [Settings to disable AI features added to the UI](https://gitlab.com/gitlab-org/gitlab/-/issues/441489) in GitLab 16.11.
|
||||
> - [Settings to turn off AI features introduced](https://gitlab.com/groups/gitlab-org/-/epics/12404) in GitLab 16.10.
|
||||
> - [Settings to turn off AI features added to the UI](https://gitlab.com/gitlab-org/gitlab/-/issues/441489) in GitLab 16.11.
|
||||
|
||||
GitLab Duo features that are Generally Available are automatically enabled for all users that have access.
|
||||
GitLab Duo features that are Generally Available are automatically turned on for all users that have access.
|
||||
In addition:
|
||||
|
||||
- If you have self-managed GitLab, you must
|
||||
[enable connectivity](#enable-gitlab-duo-on-self-managed-instances).
|
||||
[allow connectivity](#configure-gitlab-duo-on-a-self-managed-instance).
|
||||
- For some Generally Available features, like [Code Suggestions](project/repository/code_suggestions/index.md),
|
||||
[you must assign seats](../subscriptions/subscription-add-ons.md#assign-gitlab-duo-pro-seats)
|
||||
to the users you want to have access.
|
||||
|
||||
GitLab Duo features that are Experiment or Beta are disabled by default
|
||||
and [must be enabled](#enable-beta-and-experimental-features).
|
||||
GitLab Duo features that are Experiment or Beta are turned off by default
|
||||
and [must be turned on](#turn-on-beta-and-experimental-features).
|
||||
|
||||
## Enable GitLab Duo on self-managed instances
|
||||
## Configure GitLab Duo on a self-managed instance
|
||||
|
||||
To enable GitLab Duo on a self-managed instance, you must ensure connectivity exists.
|
||||
To use GitLab Duo on a self-managed instance, you must ensure connectivity exists.
|
||||
|
||||
### Enable outbound connections from GitLab instances
|
||||
### Allow outbound connections from the GitLab instance
|
||||
|
||||
- Your firewalls and HTTP/S proxy servers must allow outbound connections
|
||||
to `cloud.gitlab.com` and `customers.gitlab.com` on port `443` both with `https://`.
|
||||
- To use an HTTP/S proxy, both `gitLab_workhorse` and `gitLab_rails` must have the necessary
|
||||
[web proxy environment variables](https://docs.gitlab.com/omnibus/settings/environment-variables.html) set.
|
||||
|
||||
### Enable inbound connections from clients to GitLab instances
|
||||
### Allow inbound connections from clients to the GitLab instance
|
||||
|
||||
- GitLab instances must allow inbound connections from Duo clients (IDEs, Code Editors, and GitLab Web Frontend)
|
||||
on port 443 with `https://` and `wss://`.
|
||||
|
|
@ -46,13 +46,14 @@ To enable GitLab Duo on a self-managed instance, you must ensure connectivity ex
|
|||
Network policy restrictions on `wss://` traffic can cause issues with some GitLab Duo Chat
|
||||
services. Consider policy updates to allow these services.
|
||||
|
||||
## Disable GitLab Duo features
|
||||
## Turn off GitLab Duo features
|
||||
|
||||
You can turn off GitLab Duo for a group, project, or instance. (Note, GitLab Duo includes Code Suggestions.)
|
||||
You can turn off GitLab Duo for a group, project, or instance.
|
||||
|
||||
When GitLab Duo is turned off for a group, project, or instance:
|
||||
|
||||
- GitLab Duo features that access resources, like code, issues, and vulnerabilities, are not available.
|
||||
- Code Suggestions are not available.
|
||||
|
||||
However, GitLab Duo Chat works differently. When you turn off GitLab Duo:
|
||||
|
||||
|
|
@ -64,15 +65,15 @@ However, GitLab Duo Chat works differently. When you turn off GitLab Duo:
|
|||
- For an instance:
|
||||
- The **GitLab Duo Chat** button is not available anywhere in the UI.
|
||||
|
||||
### Disable for a group
|
||||
### Turn off for a group
|
||||
|
||||
You can disable GitLab Duo for a group.
|
||||
You can turn off GitLab Duo for a group.
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- You must have the Owner role for the group or project.
|
||||
|
||||
To disable GitLab Duo for a group:
|
||||
To turn off GitLab Duo for a group:
|
||||
|
||||
<!-- vale gitlab.Substitutions = NO -->
|
||||
1. On the left sidebar, select **Search or go to** and find your group.
|
||||
|
|
@ -90,33 +91,33 @@ An [issue exists](https://gitlab.com/gitlab-org/gitlab/-/issues/448709) for maki
|
|||
setting cascade to all groups and projects. Right now the lower-level projects and groups do not
|
||||
display the setting of the top-level group.
|
||||
|
||||
### Disable for a project
|
||||
### Turn off for a project
|
||||
|
||||
You can disable GitLab Duo for a project.
|
||||
You can turn off GitLab Duo for a project.
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- You must have the Owner role for the project.
|
||||
|
||||
To disable GitLab Duo for a project:
|
||||
To turn off GitLab Duo for a project:
|
||||
|
||||
1. Use the [GitLab GraphQL API](../api/graphql/getting_started.md)
|
||||
`projectSettingsUpdate` mutation.
|
||||
1. Disable GitLab Duo for the project by setting the `duo_features_enabled` setting to `false`.
|
||||
1. Turn off GitLab Duo for the project by setting the `duo_features_enabled` setting to `false`.
|
||||
(The default is `true`.)
|
||||
|
||||
### Disable for an instance
|
||||
### Turn off for an instance
|
||||
|
||||
DETAILS:
|
||||
**Offering:** Self-managed
|
||||
|
||||
You can disable GitLab Duo for the instance.
|
||||
You can turn off GitLab Duo for the instance.
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- You must be an administrator.
|
||||
|
||||
To disable GitLab Duo for an instance:
|
||||
To turn off GitLab Duo for an instance:
|
||||
|
||||
<!-- vale gitlab.Substitutions = NO -->
|
||||
1. On the left sidebar, at the bottom, select **Admin Area**.
|
||||
|
|
@ -131,21 +132,21 @@ NOTE:
|
|||
An [issue exists](https://gitlab.com/gitlab-org/gitlab/-/issues/441532) to allow administrators
|
||||
to override the setting for specific groups or projects.
|
||||
|
||||
## Enable Beta and Experimental features
|
||||
## Turn on Beta and Experimental features
|
||||
|
||||
Features listed as Experiment and Beta are disabled by default.
|
||||
Features listed as Experiment and Beta are turned off by default.
|
||||
These features are subject to the [Testing Agreement](https://handbook.gitlab.com/handbook/legal/testing-agreement/).
|
||||
|
||||
### Enable on GitLab.com
|
||||
### On GitLab.com
|
||||
|
||||
You can enable Experiment and Beta features for your group on GitLab.com.
|
||||
You can turn on Experiment and Beta features for your group on GitLab.com.
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- You must have the Owner role in the top-level group.
|
||||
|
||||
To enable Beta and Experimental GitLab Duo features, use the [Experiment and Beta features checkbox](group/manage.md#enable-experiment-and-beta-features).
|
||||
To turn on Beta and Experimental GitLab Duo features, use the [Experiment and Beta features checkbox](group/manage.md#enable-experiment-and-beta-features).
|
||||
|
||||
### Enable on self-managed
|
||||
### On self-managed
|
||||
|
||||
To enable Beta and Experimental GitLab Duo features for GitLab versions where GitLab Duo Chat is not yet generally available, see the [GitLab Duo Chat documentation](gitlab_duo_chat.md#for-self-managed).
|
||||
To turn on Beta and Experimental GitLab Duo features for GitLab versions where GitLab Duo Chat is not yet generally available, see the [GitLab Duo Chat documentation](gitlab_duo_chat.md#for-self-managed).
|
||||
|
|
|
|||
|
|
@ -269,7 +269,7 @@ Prerequisites:
|
|||
- You must have GitLab version 16.8 or later. You should use the latest GitLab version to benefit from the latest improvements to GitLab Duo Chat. The generally available version of GitLab Duo Chat in GitLab 16.11 has significant improvements in the quality of the answers.
|
||||
- You must have a Premium or Ultimate subscription that is [synchronized with GitLab](https://about.gitlab.com/pricing/licensing-faq/cloud-licensing/). To make sure GitLab Duo Chat works immediately, administrators can
|
||||
[manually synchronize your subscription](#manually-synchronize-your-subscription).
|
||||
- You must have [enabled network connectivity](ai_features_enable.md#enable-gitlab-duo-on-self-managed-instances).
|
||||
- You must have [enabled network connectivity](ai_features_enable.md#configure-gitlab-duo-on-a-self-managed-instance).
|
||||
- All of the users in your instance must have the latest version of their IDE extension.
|
||||
|
||||
Then, depending on the version of GitLab you have, you can enable GitLab Duo Chat.
|
||||
|
|
@ -294,7 +294,7 @@ In GitLab 16.8, 16.9, and 16.10, GitLab Duo Chat is available in Beta. To enable
|
|||
|
||||
NOTE:
|
||||
Usage of GitLab Duo Chat Beta is governed by the [GitLab Testing Agreement](https://handbook.gitlab.com/handbook/legal/testing-agreement/).
|
||||
Learn about [data usage when using GitLab Duo Chat](ai_features.md#data-usage).
|
||||
Learn about [data usage when using GitLab Duo Chat](ai_data_usage.md).
|
||||
|
||||
#### Manually synchronize your subscription
|
||||
|
||||
|
|
@ -315,7 +315,7 @@ In GitLab 16.8, 16.9, and 16.10, on GitLab Dedicated, GitLab Duo Chat is availab
|
|||
## Disable GitLab Duo Chat
|
||||
|
||||
To limit the data that Duo Chat has access to, follow the instructions for
|
||||
[disabling GitLab Duo features](ai_features_enable.md#disable-gitlab-duo-features).
|
||||
[disabling GitLab Duo features](ai_features_enable.md#turn-off-gitlab-duo-features).
|
||||
|
||||
## Use GitLab Duo Chat in the GitLab UI
|
||||
|
||||
|
|
|
|||
|
|
@ -1,58 +1,11 @@
|
|||
---
|
||||
stage: Create
|
||||
group: Code Creation
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
redirect_to: '../../../ai_data_usage.md'
|
||||
remove_date: '2024-08-08'
|
||||
---
|
||||
|
||||
# Code Suggestions data usage
|
||||
This document was moved to [another location](../../../ai_data_usage.md).
|
||||
|
||||
DETAILS:
|
||||
**Tier:** Premium or Ultimate with GitLab Duo Pro
|
||||
**Offering:** GitLab.com, Self-managed, GitLab Dedicated
|
||||
|
||||
Code Suggestions is powered by a generative AI model.
|
||||
|
||||
Your personal access token enables a secure API connection to GitLab.com or to your GitLab instance.
|
||||
This API connection securely transmits a context window from your IDE/editor to the [GitLab AI Gateway](https://gitlab.com/gitlab-org/modelops/applied-ml/code-suggestions/ai-assist), a GitLab hosted service. The [gateway](../../../../development/ai_architecture.md) calls the large language model APIs, and then the generated suggestion is transmitted back to your IDE/editor.
|
||||
|
||||
GitLab selects the best-in-class large-language models for specific tasks. We use [Google Vertex AI Code Models](https://cloud.google.com/vertex-ai/generative-ai/docs/code/code-models-overview) and [Anthropic Claude](https://www.anthropic.com/product) for Code Suggestions.
|
||||
|
||||
[View data retention policies](../../../ai_features.md#data-retention).
|
||||
|
||||
## Telemetry
|
||||
|
||||
For self-managed instances that have enabled Code Suggestions and for SaaS accounts, we collect aggregated or de-identified first-party usage data through our [Snowplow collector](https://handbook.gitlab.com/handbook/business-technology/data-team/platform/snowplow/). This usage data includes the following metrics:
|
||||
|
||||
- Language the code suggestion was in (for example, Python)
|
||||
- Editor being used (for example, VS Code)
|
||||
- Number of suggestions shown, accepted, rejected, or that had errors
|
||||
- Duration of time that a suggestion was shown
|
||||
- Prompt and suffix lengths
|
||||
- Model used
|
||||
- Number of unique users
|
||||
- Number of unique instances
|
||||
|
||||
## Inference window context
|
||||
|
||||
Code Suggestions inferences against the currently opened file, the content before and after the cursor, the filename, and the extension type. For more information on possible future context expansion to improve the quality of suggestions, see [epic 11669](https://gitlab.com/groups/gitlab-org/-/epics/11669).
|
||||
|
||||
### Truncation of file content
|
||||
|
||||
Because of LLM limits and performance reasons, the content of the currently
|
||||
opened file is truncated:
|
||||
|
||||
- **For code completion**: to 2048 tokens (roughly 8192 characters).
|
||||
- **For code generation**: to 50,000 characters.
|
||||
|
||||
Content above the cursor is prioritized over content below the cursor. The content
|
||||
above the cursor is truncated from the left side, and content below the cursor
|
||||
is truncated from the right side.
|
||||
|
||||
## Training data
|
||||
|
||||
GitLab does not train generative AI models based on private (non-public) data. The vendors we work with also do not train models based on private data.
|
||||
|
||||
For more information on GitLab Code Suggestions data [sub-processors](https://about.gitlab.com/privacy/subprocessors/#third-party-sub-processors), see:
|
||||
|
||||
- Google Vertex AI Codey APIs [data governance](https://cloud.google.com/vertex-ai/generative-ai/docs/data-governance) and [responsible AI](https://cloud.google.com/vertex-ai/generative-ai/docs/learn/responsible-ai).
|
||||
- Anthropic Claude's [constitution](https://www.anthropic.com/news/claudes-constitution).
|
||||
<!-- This redirect file can be deleted after <2024-08-08>. -->
|
||||
<!-- Redirects that point to other docs in the same project expire in three months. -->
|
||||
<!-- Redirects that point to docs in a different project or site (for example, link is not relative and starts with `https:`) expire in one year. -->
|
||||
<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html -->
|
||||
|
|
|
|||
|
|
@ -86,11 +86,32 @@ For use cases and best practices, follow the [GitLab Duo examples documentation]
|
|||
|
||||
## Response time
|
||||
|
||||
Code Suggestions is powered by a generative AI model.
|
||||
|
||||
Your personal access token enables a secure API connection to GitLab.com or to your GitLab instance.
|
||||
This API connection securely transmits a context window from your IDE/editor to the [GitLab AI Gateway](https://gitlab.com/gitlab-org/modelops/applied-ml/code-suggestions/ai-assist), a GitLab hosted service. The [gateway](../../../../development/ai_architecture.md) calls the large language model APIs, and then the generated suggestion is transmitted back to your IDE/editor.
|
||||
|
||||
- Code completion suggestions are usually low latency.
|
||||
- For code generation:
|
||||
- Algorithms or large code blocks might take more than 10 seconds to generate.
|
||||
- Streaming of code generation responses is supported in VS Code, leading to faster average response times. Other supported IDEs offer slower response times and will return the generated code in a single block.
|
||||
|
||||
## Inference window context
|
||||
|
||||
Code Suggestions inferences against the currently opened file, the content before and after the cursor, the filename, and the extension type. For more information on possible future context expansion to improve the quality of suggestions, see [epic 11669](https://gitlab.com/groups/gitlab-org/-/epics/11669).
|
||||
|
||||
## Truncation of file content
|
||||
|
||||
Because of LLM limits and performance reasons, the content of the currently
|
||||
opened file is truncated:
|
||||
|
||||
- For code completion: to 2048 tokens (roughly 8192 characters).
|
||||
- For code generation: to 50,000 characters.
|
||||
|
||||
Content above the cursor is prioritized over content below the cursor. The content
|
||||
above the cursor is truncated from the left side, and content below the cursor
|
||||
is truncated from the right side.
|
||||
|
||||
## Accuracy of results
|
||||
|
||||
We are continuing to work on the accuracy of overall generated content.
|
||||
|
|
@ -104,13 +125,6 @@ However, Code Suggestions might generate suggestions that are:
|
|||
|
||||
When using Code Suggestions, [code review best practice](../../../../development/code_review.md) still applies.
|
||||
|
||||
## Progressive enhancement
|
||||
|
||||
This feature is designed as a progressive enhancement to developer IDEs.
|
||||
Code Suggestions offer a completion if a suitable recommendation is provided to the user in a timely manner.
|
||||
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.
|
||||
|
||||
## Disable Code Suggestions
|
||||
|
||||
To disable Code Suggestions, disable the feature in your IDE editor extension.
|
||||
|
|
|
|||
|
|
@ -3,51 +3,31 @@
|
|||
require "thor"
|
||||
require "require_all"
|
||||
|
||||
require_rel "helpers/**/*.rb"
|
||||
require_rel "lib/**/*.rb"
|
||||
require_rel "commands/**/*.rb"
|
||||
|
||||
module Gitlab
|
||||
module Cng
|
||||
# Main CLI class handling all commands
|
||||
#
|
||||
class CLI < Thor
|
||||
class CLI < Commands::Command
|
||||
extend Helpers::Thor
|
||||
|
||||
# Error raised by this runner
|
||||
Error = Class.new(StandardError)
|
||||
|
||||
# Fail if unknown option is passed
|
||||
check_unknown_options!
|
||||
|
||||
class << self
|
||||
# Exit with non 0 status code if any command fails
|
||||
#
|
||||
# @return [Boolean]
|
||||
def exit_on_failure?
|
||||
true
|
||||
end
|
||||
|
||||
# Register all public methods of Thor class as top level commands
|
||||
#
|
||||
# @param [Thor] klass
|
||||
# @return [void]
|
||||
def register_commands(klass)
|
||||
raise Error, "#{klass} is not a Thor class" unless klass < Thor
|
||||
|
||||
klass.commands.each do |name, command|
|
||||
raise Error, "Tried to register command '#{name}' but the command already exists" if commands[name]
|
||||
|
||||
# check if the method takes arguments
|
||||
pass_args = klass.new.method(name).arity != 0
|
||||
|
||||
commands[name] = command
|
||||
define_method(name) do |*args|
|
||||
pass_args ? invoke(klass, name, *args) : invoke(klass, name)
|
||||
end
|
||||
end
|
||||
end
|
||||
# Exit with non 0 status code if any command fails
|
||||
#
|
||||
# @return [Boolean]
|
||||
def self.exit_on_failure?
|
||||
true
|
||||
end
|
||||
|
||||
register_commands(Commands::Version)
|
||||
register_commands(Commands::Doctor)
|
||||
|
||||
desc "create [SUBCOMMAND]", "Manage deployment related object creation"
|
||||
subcommand "create", Commands::Create
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,8 +3,21 @@
|
|||
module Gitlab
|
||||
module Cng
|
||||
module Commands
|
||||
# Thor command base class
|
||||
#
|
||||
class Command < Thor
|
||||
include Helpers::Output
|
||||
|
||||
check_unknown_options!
|
||||
|
||||
private
|
||||
|
||||
# Options hash with symbolized keys
|
||||
#
|
||||
# @return [Hash]
|
||||
def symbolized_options
|
||||
@symbolized_options ||= options.transform_keys(&:to_sym)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Cng
|
||||
module Commands
|
||||
# Create command composed of subcommands that create various resources needed for CNG deployment
|
||||
#
|
||||
class Create < Command
|
||||
desc "cluster", "Create kind cluster for local deployments"
|
||||
option :name,
|
||||
desc: "Cluster name",
|
||||
default: "gitlab",
|
||||
type: :string,
|
||||
aliases: "-n"
|
||||
option :ci,
|
||||
desc: "Use CI specific configuration",
|
||||
default: false,
|
||||
type: :boolean,
|
||||
aliases: "-c"
|
||||
option :docker_hostname,
|
||||
desc: "Custom docker hostname if remote docker instance is used, like docker-in-docker",
|
||||
type: :string,
|
||||
aliases: "-d"
|
||||
def cluster
|
||||
Kind::Cluster.new(**symbolized_options).create
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -5,12 +5,14 @@ require "tty-which"
|
|||
module Gitlab
|
||||
module Cng
|
||||
module Commands
|
||||
# Command to check system dependencies
|
||||
#
|
||||
class Doctor < Command
|
||||
TOOLS = %w[docker kind kubectl helm].freeze
|
||||
|
||||
desc "doctor", "Validate presence of all required system dependencies"
|
||||
def doctor
|
||||
log_info "Checking system dependencies", bright: true
|
||||
log "Checking system dependencies", :info, bright: true
|
||||
missing_tools = TOOLS.filter_map do |tool|
|
||||
Helpers::Spinner.spin("Checking if #{tool} is installed") do
|
||||
raise "#{tool} not found in PATH" unless TTY::Which.exist?(tool)
|
||||
|
|
@ -18,7 +20,7 @@ module Gitlab
|
|||
rescue StandardError
|
||||
tool
|
||||
end
|
||||
return log_success "All system dependencies are present", bright: true if missing_tools.empty?
|
||||
return log "All system dependencies are present", :success, bright: true if missing_tools.empty?
|
||||
|
||||
exit_with_error "The following system dependencies are missing: #{missing_tools.join(', ')}"
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,8 +3,10 @@
|
|||
module Gitlab
|
||||
module Cng
|
||||
module Commands
|
||||
# Basic command to print the version of cng orchestrator
|
||||
#
|
||||
class Version < Command
|
||||
desc "version", "Prints cng orchestrator version"
|
||||
desc "version", "Print cng orchestrator version"
|
||||
def version
|
||||
puts Cng::VERSION
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,88 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "rainbow"
|
||||
|
||||
module Gitlab
|
||||
module Cng
|
||||
module Helpers
|
||||
# Console output helpers to include in command implementations
|
||||
#
|
||||
module Output
|
||||
private
|
||||
|
||||
# Print base output without specific color
|
||||
#
|
||||
# @param [String] message
|
||||
# @param [Boolean] bright
|
||||
# @return [void]
|
||||
def log(message, bright: false)
|
||||
puts colorize(message, nil, bright: bright)
|
||||
end
|
||||
|
||||
# Print info in magenta color
|
||||
#
|
||||
# @param [String] message
|
||||
# @param [Boolean] bright
|
||||
# @return [nil]
|
||||
def log_info(message, bright: false)
|
||||
puts colorize(message, :magenta, bright: bright)
|
||||
end
|
||||
|
||||
# Print success message in green color
|
||||
#
|
||||
# @param [String] message
|
||||
# @param [Boolean] bright
|
||||
# @return [nil]
|
||||
def log_success(message, bright: false)
|
||||
puts colorize(message, :green, bright: bright)
|
||||
end
|
||||
|
||||
# Print warning message in yellow color
|
||||
#
|
||||
# @param [String] message
|
||||
# @param [Boolean] bright
|
||||
# @return [nil]
|
||||
def log_warn(message, bright: false)
|
||||
puts colorize(message, :yellow, bright: bright)
|
||||
end
|
||||
|
||||
# Print error message in red color
|
||||
#
|
||||
# @param [String] message
|
||||
# @param [Boolean] bright
|
||||
# @return [nil]
|
||||
def log_error(message, bright: false)
|
||||
puts colorize(message, :red, bright: bright)
|
||||
end
|
||||
|
||||
# Exit with non zero exit code and print error message
|
||||
#
|
||||
# @param [String] message
|
||||
# @return [void]
|
||||
def exit_with_error(message)
|
||||
log_error(message, bright: true)
|
||||
exit 1
|
||||
end
|
||||
|
||||
# Colorize message string and output to stdout
|
||||
#
|
||||
# @param [String] message
|
||||
# @param [<Symbol, nil>] color
|
||||
# @param [Boolean] bright
|
||||
# @return [String]
|
||||
def colorize(message, color, bright: false)
|
||||
rainbow.wrap(message)
|
||||
.then { |m| bright ? m.bright : m }
|
||||
.then { |m| color ? m.color(color) : m }
|
||||
end
|
||||
|
||||
# Instance of rainbow colorization class
|
||||
#
|
||||
# @return [Rainbow]
|
||||
def rainbow
|
||||
@rainbow ||= Rainbow.new
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "open3"
|
||||
|
||||
module Gitlab
|
||||
module Cng
|
||||
module Helpers
|
||||
# Wrapper for shell command execution
|
||||
#
|
||||
module Shell
|
||||
CommandFailure = Class.new(StandardError)
|
||||
|
||||
# Execute shell command
|
||||
#
|
||||
# @param [String] command
|
||||
# @return [String] output
|
||||
def self.execute_shell(command)
|
||||
out, err, status = Open3.capture3(command)
|
||||
|
||||
cmd_output = []
|
||||
cmd_output << "Out: #{out}" unless out.empty?
|
||||
cmd_output << "Err: #{err}" unless err.empty?
|
||||
output = cmd_output.join("\n")
|
||||
|
||||
unless status.success?
|
||||
err_msg = "Command '#{command}' failed!\n#{output}"
|
||||
raise(CommandFailure, err_msg)
|
||||
end
|
||||
|
||||
output
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "rainbow"
|
||||
|
||||
module Gitlab
|
||||
module Cng
|
||||
module Helpers
|
||||
# Console output helpers to include in command implementations
|
||||
#
|
||||
module Output
|
||||
LOG_COLOR = {
|
||||
default: nil,
|
||||
info: :magenta,
|
||||
success: :green,
|
||||
warn: :yellow,
|
||||
error: :red
|
||||
}.freeze
|
||||
|
||||
private
|
||||
|
||||
# Print colorized log message to stdout
|
||||
#
|
||||
# @param [String] message
|
||||
# @param [Symbol] type
|
||||
# @param [Boolean] bright
|
||||
# @return [void]
|
||||
def log(message, type = :default, bright: false)
|
||||
puts colorize(message, LOG_COLOR.fetch(type), bright: bright)
|
||||
end
|
||||
|
||||
# Exit with non zero exit code and print error message
|
||||
#
|
||||
# @param [String] message
|
||||
# @return [void]
|
||||
def exit_with_error(message)
|
||||
log(message, :error, bright: true)
|
||||
exit 1
|
||||
end
|
||||
|
||||
# Colorize message string and output to stdout
|
||||
#
|
||||
# @param [String] message
|
||||
# @param [<Symbol, nil>] color
|
||||
# @param [Boolean] bright
|
||||
# @return [String]
|
||||
def colorize(message, color, bright: false)
|
||||
rainbow.wrap(message)
|
||||
.then { |m| bright ? m.bright : m }
|
||||
.then { |m| color ? m.color(color) : m }
|
||||
end
|
||||
|
||||
# Instance of rainbow colorization class
|
||||
#
|
||||
# @return [Rainbow]
|
||||
def rainbow
|
||||
@rainbow ||= Rainbow.new
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "open3"
|
||||
|
||||
module Gitlab
|
||||
module Cng
|
||||
module Helpers
|
||||
# Wrapper for shell command execution
|
||||
#
|
||||
module Shell
|
||||
CommandFailure = Class.new(StandardError)
|
||||
|
||||
# Execute shell command
|
||||
#
|
||||
# @param [Array] command
|
||||
# @param [Boolean] raise_on_failure
|
||||
# @param [Hash] env
|
||||
# @return [<String, Array>] return command output and status if raise_on_failure is false
|
||||
def execute_shell(cmd, raise_on_failure: true, env: {})
|
||||
raise "System commands must be given as an array of strings" unless cmd.is_a?(Array)
|
||||
|
||||
if cmd.one? && cmd.first.match?(/\s/)
|
||||
raise "System commands must be split into an array of space-separated values"
|
||||
end
|
||||
|
||||
out, status = Open3.capture2e(env, *cmd)
|
||||
|
||||
if raise_on_failure && !status.success?
|
||||
err_msg = "Command '#{cmd}' failed!\n#{out}"
|
||||
raise(CommandFailure, err_msg)
|
||||
end
|
||||
|
||||
raise_on_failure ? out : [out, status]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -5,7 +5,7 @@ require "tty-spinner"
|
|||
module Gitlab
|
||||
module Cng
|
||||
module Helpers
|
||||
# Spinner helper class
|
||||
# Spinner helper class for wrapping long running tasks in progress spinner
|
||||
#
|
||||
class Spinner
|
||||
include Output
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Cng
|
||||
module Helpers
|
||||
# Thor command specific helpers
|
||||
#
|
||||
module Thor
|
||||
# Register all public methods of Thor class inside another Thor class as commands
|
||||
#
|
||||
# @param [Thor] klass
|
||||
# @return [void]
|
||||
def register_commands(klass)
|
||||
raise "#{klass} is not a Thor class" unless klass < ::Thor
|
||||
|
||||
klass.commands.each do |name, command|
|
||||
raise "Tried to register command '#{name}' but the command already exists" if commands[name]
|
||||
|
||||
# check if the method takes arguments
|
||||
pass_args = klass.new.method(name).arity != 0
|
||||
|
||||
commands[name] = command
|
||||
define_method(name) do |*args|
|
||||
pass_args ? invoke(klass, name, *args) : invoke(klass, name)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "uri"
|
||||
|
||||
require_relative "configs"
|
||||
|
||||
module Gitlab
|
||||
module Cng
|
||||
module Kind
|
||||
# Class responsible for creating kind cluster
|
||||
#
|
||||
class Cluster
|
||||
include Helpers::Output
|
||||
include Helpers::Shell
|
||||
include Configs
|
||||
|
||||
def initialize(ci:, name:, docker_hostname: nil)
|
||||
@ci = ci
|
||||
@name = name
|
||||
@docker_hostname = ci ? docker_hostname || "docker" : docker_hostname
|
||||
end
|
||||
|
||||
def create
|
||||
log "Creating cluster '#{name}'", :info
|
||||
return log " cluster '#{name}' already exists, skipping!" if cluster_exists?
|
||||
|
||||
create_cluster
|
||||
update_server_url
|
||||
log "Cluster '#{name}' created", :success
|
||||
rescue Helpers::Shell::CommandFailure
|
||||
# Exit cleanly without stacktrace if shell command fails
|
||||
exit(1)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :ci, :name, :docker_hostname
|
||||
|
||||
# Check if cluster exists
|
||||
#
|
||||
# @return [Boolean]
|
||||
def cluster_exists?
|
||||
execute_shell(%w[kind get clusters]).include?(name)
|
||||
end
|
||||
|
||||
# Create kind cluster
|
||||
#
|
||||
# @return [void]
|
||||
def create_cluster
|
||||
Helpers::Spinner.spin("performing cluster creation") do
|
||||
execute_shell([
|
||||
"kind",
|
||||
"create",
|
||||
"cluster",
|
||||
"--name", name,
|
||||
"--wait", "10s",
|
||||
"--config", ci ? ci_config(docker_hostname) : default_config(docker_hostname)
|
||||
])
|
||||
end
|
||||
end
|
||||
|
||||
# Update server url in kubeconfig for kubectl to work correctly with remote docker
|
||||
#
|
||||
# @return [void]
|
||||
def update_server_url
|
||||
return unless docker_hostname
|
||||
|
||||
Helpers::Spinner.spin("updating kind cluster server url") do
|
||||
cluster_name = "kind-#{name}"
|
||||
server = execute_shell([
|
||||
"kubectl", "config", "view",
|
||||
"-o", "jsonpath={.clusters[?(@.name == \"#{cluster_name}\")].cluster.server}"
|
||||
])
|
||||
uri = URI.parse(server).tap { |uri| uri.host = docker_hostname }
|
||||
execute_shell(%W[kubectl config set-cluster #{cluster_name} --server=#{uri}])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "tmpdir"
|
||||
require "erb"
|
||||
|
||||
module Gitlab
|
||||
module Cng
|
||||
module Kind
|
||||
# Kind configuration file templates
|
||||
#
|
||||
module Configs
|
||||
# Temporary ci specific kind configuration file
|
||||
#
|
||||
# @param [String] docker_hostname
|
||||
# @return [String] file path
|
||||
def ci_config(docker_hostname)
|
||||
config_yml = <<~YML
|
||||
apiVersion: kind.x-k8s.io/v1alpha4
|
||||
kind: Cluster
|
||||
networking:
|
||||
apiServerAddress: "0.0.0.0"
|
||||
nodes:
|
||||
- role: control-plane
|
||||
kubeadmConfigPatches:
|
||||
- |
|
||||
kind: InitConfiguration
|
||||
nodeRegistration:
|
||||
kubeletExtraArgs:
|
||||
node-labels: "ingress-ready=true"
|
||||
- |
|
||||
kind: ClusterConfiguration
|
||||
apiServer:
|
||||
certSANs:
|
||||
- "#{docker_hostname}"
|
||||
extraPortMappings:
|
||||
# containerPort below must match the values file:
|
||||
# nginx-ingress.controller.service.nodePorts.http
|
||||
- containerPort: 32080
|
||||
hostPort: 80
|
||||
listenAddress: "0.0.0.0"
|
||||
# containerPort below must match the values file:
|
||||
# nginx-ingress.controller.service.nodePorts.gitlab-shell
|
||||
- containerPort: 32022
|
||||
hostPort: 22
|
||||
listenAddress: "0.0.0.0"
|
||||
YML
|
||||
|
||||
tmp_config_file(config_yml)
|
||||
end
|
||||
|
||||
# Temporary kind configuration file
|
||||
#
|
||||
# @param [String, nil] docker_hostname
|
||||
# @return [String] file path
|
||||
def default_config(docker_hostname)
|
||||
template = ERB.new(<<~YML, trim_mode: "-")
|
||||
kind: Cluster
|
||||
apiVersion: kind.x-k8s.io/v1alpha4
|
||||
nodes:
|
||||
- role: control-plane
|
||||
kubeadmConfigPatches:
|
||||
- |
|
||||
kind: InitConfiguration
|
||||
nodeRegistration:
|
||||
kubeletExtraArgs:
|
||||
node-labels: "ingress-ready=true"
|
||||
<% if docker_hostname -%>
|
||||
- |
|
||||
kind: ClusterConfiguration
|
||||
apiServer:
|
||||
certSANs:
|
||||
- "<%= docker_hostname %>"
|
||||
<% end -%>
|
||||
extraPortMappings:
|
||||
# containerPort below must match the values file:
|
||||
# nginx-ingress.controller.service.nodePorts.http
|
||||
- containerPort: 32080
|
||||
hostPort: 32080
|
||||
listenAddress: "0.0.0.0"
|
||||
# containerPort below must match the values file:
|
||||
# nginx-ingress.controller.service.nodePorts.ssh
|
||||
- containerPort: 32022
|
||||
hostPort: 32022
|
||||
listenAddress: "0.0.0.0"
|
||||
YML
|
||||
|
||||
tmp_config_file(template.result(binding))
|
||||
end
|
||||
|
||||
# Create temporary kind config file
|
||||
#
|
||||
# @param [String] config_yml
|
||||
# @return [String]
|
||||
def tmp_config_file(config_yml)
|
||||
File.join(Dir.tmpdir, "kind-config.yml").tap do |path|
|
||||
File.write(path, config_yml)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.shared_context "with command testing helper" do
|
||||
let(:command_instance) { described_class.new }
|
||||
|
||||
# Invoke command with args
|
||||
#
|
||||
# @param [String] command
|
||||
# @param [Array] args
|
||||
# @return [void]
|
||||
def invoke_command(command, args = [], options = {})
|
||||
command_instance.invoke(command, args, options)
|
||||
end
|
||||
|
||||
# Expect command to have attributes
|
||||
#
|
||||
# @param [String] command
|
||||
# @param [Hash] attributes
|
||||
# @return [void]
|
||||
def expect_command_to_include_attributes(command, attributes)
|
||||
expect(described_class.commands[command].to_h).to include(attributes)
|
||||
end
|
||||
end
|
||||
|
|
@ -4,9 +4,10 @@ RSpec.describe "cng" do
|
|||
let(:usage) do
|
||||
<<~USAGE
|
||||
Commands:
|
||||
cng doctor # Validate presence of all required system dependencies
|
||||
cng help [COMMAND] # Describe available commands or one specific command
|
||||
cng version # Prints cng orchestrator version
|
||||
cng create [SUBCOMMAND] # Manage deployment related object creation
|
||||
cng doctor # Validate presence of all required system dependencies
|
||||
cng help [COMMAND] # Describe available commands or one specific command
|
||||
cng version # Print cng orchestrator version
|
||||
USAGE
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,55 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe Gitlab::Cng::CLI do
|
||||
shared_examples "command with help" do |args, help_output|
|
||||
it "shows help" do
|
||||
expect { cli.invoke(*args) }.to output(/#{help_output}/).to_stdout
|
||||
end
|
||||
end
|
||||
|
||||
subject(:cli) { described_class.new }
|
||||
|
||||
describe "version command" do
|
||||
it_behaves_like "command with help", [:help, ["version"]], /Print cng orchestrator version/
|
||||
|
||||
it "executes version command" do
|
||||
expect { cli.invoke(:version) }.to output(/#{Gitlab::Cng::VERSION}/o).to_stdout
|
||||
end
|
||||
end
|
||||
|
||||
describe "doctor command" do
|
||||
let(:command_instance) { Gitlab::Cng::Commands::Doctor.new }
|
||||
|
||||
before do
|
||||
allow(Gitlab::Cng::Commands::Doctor).to receive(:new).and_return(command_instance)
|
||||
allow(command_instance).to receive(:doctor)
|
||||
end
|
||||
|
||||
it_behaves_like "command with help", [:help, ["doctor"]], /Validate presence of all required system dependencies/
|
||||
|
||||
it "invokes doctor command" do
|
||||
cli.invoke(:doctor)
|
||||
|
||||
expect(command_instance).to have_received(:doctor)
|
||||
end
|
||||
end
|
||||
|
||||
describe "create command" do
|
||||
context "with cluster subcommand" do
|
||||
let(:cluster_instance) { instance_double(Gitlab::Cng::Kind::Cluster, create: nil) }
|
||||
|
||||
before do
|
||||
allow(Gitlab::Cng::Kind::Cluster).to receive(:new).and_return(cluster_instance)
|
||||
end
|
||||
|
||||
it_behaves_like "command with help", [:help, %w[create cluster]], /Create kind cluster for local deployments/
|
||||
|
||||
it "invokes cluster create command" do
|
||||
cli.invoke(:create, %w[cluster])
|
||||
|
||||
expect(Gitlab::Cng::Kind::Cluster).to have_received(:new).with(ci: false, name: "gitlab")
|
||||
expect(cluster_instance).to have_received(:create)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe Gitlab::Cng::CLI do
|
||||
let(:cli) { described_class.new }
|
||||
|
||||
describe "version command" do
|
||||
it "shows version command help" do
|
||||
expect { cli.invoke(:help, ["version"]) }.to output(/Prints cng orchestrator version/).to_stdout
|
||||
end
|
||||
|
||||
it "executes version command" do
|
||||
expect { cli.invoke(:version) }.to output(/#{Gitlab::Cng::VERSION}/o).to_stdout
|
||||
end
|
||||
end
|
||||
|
||||
describe "doctor command" do
|
||||
let(:command_instance) { Gitlab::Cng::Commands::Doctor.new }
|
||||
|
||||
before do
|
||||
allow(Gitlab::Cng::Commands::Doctor).to receive(:new).and_return(command_instance)
|
||||
allow(command_instance).to receive(:doctor)
|
||||
end
|
||||
|
||||
it "shows doctor command help" do
|
||||
expect { cli.invoke(:help, ["doctor"]) }.to output(
|
||||
/Validate presence of all required system dependencies/
|
||||
).to_stdout
|
||||
end
|
||||
|
||||
it "invokes doctor command" do
|
||||
cli.invoke(:doctor)
|
||||
|
||||
expect(command_instance).to have_received(:doctor)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "rspec"
|
||||
|
||||
require "gitlab/cng/cli"
|
||||
|
||||
require_relative "command_helper"
|
||||
|
||||
RSpec.configure do |config|
|
||||
# Disable RSpec exposing methods globally on `Module` and `main`
|
||||
config.disable_monkey_patching!
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe Gitlab::Cng::Commands::Create do
|
||||
include_context "with command testing helper"
|
||||
|
||||
describe "cluster command" do
|
||||
let(:command_name) { "cluster" }
|
||||
let(:kind_cluster) { instance_double(Gitlab::Cng::Kind::Cluster, create: nil) }
|
||||
|
||||
before do
|
||||
allow(Gitlab::Cng::Kind::Cluster).to receive(:new).and_return(kind_cluster)
|
||||
end
|
||||
|
||||
it "defines cluster command" do
|
||||
expect_command_to_include_attributes(command_name, {
|
||||
description: "Create kind cluster for local deployments",
|
||||
name: command_name,
|
||||
usage: command_name
|
||||
})
|
||||
end
|
||||
|
||||
it "invokes kind cluster creation with correct arguments" do
|
||||
invoke_command(command_name, [], { ci: true, name: "test-cluster" })
|
||||
|
||||
expect(kind_cluster).to have_received(:create)
|
||||
expect(Gitlab::Cng::Kind::Cluster).to have_received(:new).with({
|
||||
ci: true,
|
||||
name: "test-cluster"
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe Gitlab::Cng::Commands::Doctor do
|
||||
subject(:command) { described_class.new }
|
||||
include_context "with command testing helper"
|
||||
|
||||
let(:spinner) { instance_double(Gitlab::Cng::Helpers::Spinner) }
|
||||
let(:command_name) { "doctor" }
|
||||
|
|
@ -11,13 +11,12 @@ RSpec.describe Gitlab::Cng::Commands::Doctor do
|
|||
before do
|
||||
allow(Gitlab::Cng::Helpers::Spinner).to receive(:new) { spinner }
|
||||
allow(spinner).to receive(:spin).and_yield
|
||||
allow(command).to receive(:puts)
|
||||
|
||||
tools.each { |tool| allow(TTY::Which).to receive(:exist?).with(tool).and_return(tools_present) }
|
||||
end
|
||||
|
||||
it "defines a doctor command" do
|
||||
expect(described_class.commands[command_name].to_h).to include({
|
||||
expect_command_to_include_attributes(command_name, {
|
||||
description: "Validate presence of all required system dependencies",
|
||||
long_description: nil,
|
||||
name: command_name,
|
||||
|
|
@ -28,8 +27,9 @@ RSpec.describe Gitlab::Cng::Commands::Doctor do
|
|||
|
||||
context "with all tools present" do
|
||||
it "does not raise an error", :aggregate_failures do
|
||||
expect { command.doctor }.not_to raise_error
|
||||
expect(command).to have_received(:puts).with(/All system dependencies are present/)
|
||||
expect do
|
||||
expect { invoke_command(command_name) }.not_to raise_error
|
||||
end.to output(/All system dependencies are present/).to_stdout
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -37,8 +37,9 @@ RSpec.describe Gitlab::Cng::Commands::Doctor do
|
|||
let(:tools_present) { false }
|
||||
|
||||
it "exits and prints missing dependencies error", :aggregate_failures do
|
||||
expect { command.doctor }.to raise_error(SystemExit)
|
||||
expect(command).to have_received(:puts).with(/The following system dependencies are missing: #{tools.join(', ')}/)
|
||||
expect do
|
||||
expect { invoke_command(command_name) }.to raise_error(SystemExit)
|
||||
end.to output(/The following system dependencies are missing: #{tools.join(', ')}/).to_stdout
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,11 +1,13 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe Gitlab::Cng::Commands::Version do
|
||||
include_context "with command testing helper"
|
||||
|
||||
let(:version) { Gitlab::Cng::VERSION }
|
||||
|
||||
it "defines a version command" do
|
||||
expect(described_class.commands["version"].to_h).to include({
|
||||
description: "Prints cng orchestrator version",
|
||||
expect_command_to_include_attributes("version", {
|
||||
description: "Print cng orchestrator version",
|
||||
long_description: nil,
|
||||
name: "version",
|
||||
options: {},
|
||||
|
|
@ -14,6 +16,6 @@ RSpec.describe Gitlab::Cng::Commands::Version do
|
|||
end
|
||||
|
||||
it "prints the version" do
|
||||
expect { described_class.new.version }.to output(/#{version}/).to_stdout
|
||||
expect { invoke_command("version") }.to output(/#{version}/).to_stdout
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,137 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe Gitlab::Cng::Kind::Cluster do
|
||||
subject(:cluster) { described_class.new(ci: ci, name: name, docker_hostname: docker_hostname) }
|
||||
|
||||
let(:ci) { false }
|
||||
let(:name) { "gitlab" }
|
||||
let(:docker_hostname) { nil }
|
||||
let(:tmp_config_path) { File.join(Dir.tmpdir, "kind-config.yml") }
|
||||
let(:command_status) { instance_double(Process::Status, success?: true) }
|
||||
let(:clusters) { "kind" }
|
||||
|
||||
before do
|
||||
allow(Gitlab::Cng::Helpers::Spinner).to receive(:spin).and_yield
|
||||
allow(File).to receive(:write).with(tmp_config_path, kind_config_content)
|
||||
|
||||
allow(Open3).to receive(:capture2e).with({}, *%w[
|
||||
kind get clusters
|
||||
]).and_return([clusters, command_status])
|
||||
allow(Open3).to receive(:capture2e).with({}, *[
|
||||
"kind",
|
||||
"create",
|
||||
"cluster",
|
||||
"--name", name,
|
||||
"--wait", "10s",
|
||||
"--config", tmp_config_path
|
||||
]).and_return(["", command_status])
|
||||
end
|
||||
|
||||
context "with ci specific setup" do
|
||||
let(:ci) { true }
|
||||
let(:docker_hostname) { "docker" }
|
||||
|
||||
let(:kind_config_content) do
|
||||
<<~YML
|
||||
apiVersion: kind.x-k8s.io/v1alpha4
|
||||
kind: Cluster
|
||||
networking:
|
||||
apiServerAddress: "0.0.0.0"
|
||||
nodes:
|
||||
- role: control-plane
|
||||
kubeadmConfigPatches:
|
||||
- |
|
||||
kind: InitConfiguration
|
||||
nodeRegistration:
|
||||
kubeletExtraArgs:
|
||||
node-labels: "ingress-ready=true"
|
||||
- |
|
||||
kind: ClusterConfiguration
|
||||
apiServer:
|
||||
certSANs:
|
||||
- "#{docker_hostname}"
|
||||
extraPortMappings:
|
||||
# containerPort below must match the values file:
|
||||
# nginx-ingress.controller.service.nodePorts.http
|
||||
- containerPort: 32080
|
||||
hostPort: 80
|
||||
listenAddress: "0.0.0.0"
|
||||
# containerPort below must match the values file:
|
||||
# nginx-ingress.controller.service.nodePorts.gitlab-shell
|
||||
- containerPort: 32022
|
||||
hostPort: 22
|
||||
listenAddress: "0.0.0.0"
|
||||
YML
|
||||
end
|
||||
|
||||
context "without existing cluster" do
|
||||
before do
|
||||
allow(Open3).to receive(:capture2e).with({}, *[
|
||||
"kubectl", "config", "view", "-o", "jsonpath={.clusters[?(@.name == \"kind-#{name}\")].cluster.server}"
|
||||
]).and_return(["https://127.0.0.1:6443", command_status])
|
||||
allow(Open3).to receive(:capture2e).with({}, *%W[
|
||||
kubectl config set-cluster kind-#{name} --server=https://#{docker_hostname}:6443
|
||||
]).and_return(["", command_status])
|
||||
end
|
||||
|
||||
it "creates cluster with ci specific configuration" do
|
||||
expect { cluster.create }.to output(/Cluster '#{name}' created/).to_stdout
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "without ci specific setup" do
|
||||
let(:ci) { false }
|
||||
let(:docker_hostname) { nil }
|
||||
|
||||
let(:kind_config_content) do
|
||||
<<~YML
|
||||
kind: Cluster
|
||||
apiVersion: kind.x-k8s.io/v1alpha4
|
||||
nodes:
|
||||
- role: control-plane
|
||||
kubeadmConfigPatches:
|
||||
- |
|
||||
kind: InitConfiguration
|
||||
nodeRegistration:
|
||||
kubeletExtraArgs:
|
||||
node-labels: "ingress-ready=true"
|
||||
extraPortMappings:
|
||||
# containerPort below must match the values file:
|
||||
# nginx-ingress.controller.service.nodePorts.http
|
||||
- containerPort: 32080
|
||||
hostPort: 32080
|
||||
listenAddress: "0.0.0.0"
|
||||
# containerPort below must match the values file:
|
||||
# nginx-ingress.controller.service.nodePorts.ssh
|
||||
- containerPort: 32022
|
||||
hostPort: 32022
|
||||
listenAddress: "0.0.0.0"
|
||||
YML
|
||||
end
|
||||
|
||||
context "with already created cluster" do
|
||||
let(:clusters) { "kind\n#{name}" }
|
||||
|
||||
it "skips clusters creation" do
|
||||
expect { cluster.create }.to output(/cluster '#{name}' already exists, skipping!/).to_stdout
|
||||
end
|
||||
end
|
||||
|
||||
context "without existing cluster" do
|
||||
it "creates cluster with default config" do
|
||||
expect { cluster.create }.to output(/Cluster '#{name}' created/).to_stdout
|
||||
end
|
||||
end
|
||||
|
||||
context "with command failure" do
|
||||
let(:command_status) { instance_double(Process::Status, success?: false) }
|
||||
|
||||
it "exits on command failures" do
|
||||
expect do
|
||||
expect { cluster.create }.to output.to_stdout
|
||||
end.to raise_error(SystemExit)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -86,7 +86,7 @@ module API
|
|||
search_scope: search_scope
|
||||
)
|
||||
|
||||
Gitlab::UsageDataCounters::SearchCounter.count(:all_searches)
|
||||
Gitlab::InternalEvents.track_event('perform_search', category: 'API::Search', user: current_user)
|
||||
|
||||
paginate(@results)
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ module Gitlab
|
|||
def initialize(bridge)
|
||||
@bridge = bridge
|
||||
|
||||
context = Context.new(all_bridge_variables: bridge.variables, expand_file_refs: bridge.expand_file_refs?)
|
||||
context = Context.new(all_bridge_variables: bridge.variables, expand_file_refs: false)
|
||||
|
||||
@raw_variable_generator = RawVariableGenerator.new(context)
|
||||
@expandable_variable_generator = ExpandableVariableGenerator.new(context)
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ module Gitlab
|
|||
# Color mode ID used when no `default_color_mode` configuration setting is provided.
|
||||
APPLICATION_DEFAULT = 1
|
||||
APPLICATION_DARK = 2
|
||||
APPLICATION_SYSTEM = 3
|
||||
|
||||
# Struct class representing a single Mode
|
||||
Mode = Struct.new(:id, :name, :css_class)
|
||||
|
|
@ -14,7 +15,8 @@ module Gitlab
|
|||
def self.available_modes
|
||||
[
|
||||
Mode.new(APPLICATION_DEFAULT, s_('ColorMode|Light'), 'gl-light'),
|
||||
Mode.new(APPLICATION_DARK, s_('ColorMode|Dark (Experiment)'), 'gl-dark')
|
||||
Mode.new(APPLICATION_DARK, s_('ColorMode|Dark (Experiment)'), 'gl-dark'),
|
||||
Mode.new(APPLICATION_SYSTEM, s_('ColorMode|Auto (Experiment)'), 'gl-system')
|
||||
]
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ module Gitlab
|
|||
next unless sha.present? && commit_by(oid: sha)
|
||||
|
||||
@keeparound_requested_counter.increment(labels)
|
||||
Gitlab::AppLogger.info(message: 'Requesting keep-around reference', object_id: sha)
|
||||
|
||||
next if kept_around?(sha)
|
||||
|
||||
|
|
@ -42,8 +43,10 @@ module Gitlab
|
|||
raw_repository.write_ref(keep_around_ref_name(sha), sha)
|
||||
|
||||
@keeparound_created_counter.increment(labels)
|
||||
Gitlab::AppLogger.info(message: 'Created keep-around reference', object_id: sha)
|
||||
|
||||
rescue Gitlab::Git::CommandError => ex
|
||||
Gitlab::AppLogger.error "Unable to create keep-around reference for repository #{disk_path}: #{ex}"
|
||||
Gitlab::ErrorTracking.track_exception(ex, object_id: sha)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ module Gitlab
|
|||
gon.asset_host = ActionController::Base.asset_host
|
||||
gon.webpack_public_path = webpack_public_path
|
||||
gon.relative_url_root = Gitlab.config.gitlab.relative_url_root
|
||||
gon.user_color_mode = Gitlab::ColorModes.for_user(current_user).css_class
|
||||
gon.user_color_scheme = Gitlab::ColorSchemes.for_user(current_user).css_class
|
||||
gon.markdown_surround_selection = current_user&.markdown_surround_selection
|
||||
gon.markdown_automatic_lists = current_user&.markdown_automatic_lists
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ module Gitlab
|
|||
DiffsCounter,
|
||||
KubernetesAgentCounter,
|
||||
NoteCounter,
|
||||
SearchCounter,
|
||||
WebIdeCounter,
|
||||
SourceCodeCounter,
|
||||
MergeRequestWidgetExtensionCounter
|
||||
|
|
|
|||
|
|
@ -1,24 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module UsageDataCounters
|
||||
class SearchCounter < BaseCounter
|
||||
KNOWN_EVENTS = %w[all_searches navbar_searches].freeze
|
||||
PREFIX = nil
|
||||
|
||||
class << self
|
||||
def redis_key(event)
|
||||
require_known_event(event)
|
||||
|
||||
"#{event}_COUNT".upcase
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def counter_key(event)
|
||||
event.to_s.to_sym
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -26,3 +26,5 @@
|
|||
'{event_counters}_view_wiki_page': USAGE_WIKI_PAGES_VIEW
|
||||
'{event_counters}_click_full_report_license_compliance': USAGE_USERS_VISITING_TESTING_LICENSE_COMPLIANCE_FULL_REPORT
|
||||
'{event_counters}_click_external_link_license_compliance': USAGE_USERS_CLICKING_LICENSE_TESTING_VISITING_EXTERNAL_WEBSITE
|
||||
'{event_counters}_perform_search': ALL_SEARCHES_COUNT
|
||||
'{event_counters}_perform_navbar_search': NAVBAR_SEARCHES_COUNT
|
||||
|
|
|
|||
|
|
@ -12657,6 +12657,9 @@ msgstr ""
|
|||
msgid "Color"
|
||||
msgstr ""
|
||||
|
||||
msgid "ColorMode|Auto (Experiment)"
|
||||
msgstr ""
|
||||
|
||||
msgid "ColorMode|Dark (Experiment)"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -26111,7 +26114,7 @@ msgstr ""
|
|||
msgid "IdentityVerification|Before you can create additional groups, we need to verify your account."
|
||||
msgstr ""
|
||||
|
||||
msgid "IdentityVerification|Before you can run concurrent pipelines, we need to verify your account."
|
||||
msgid "IdentityVerification|Before you can run pipelines, we need to verify your account."
|
||||
msgstr ""
|
||||
|
||||
msgid "IdentityVerification|Before you finish creating your account, we need to verify your identity. On the verification page, enter the following code."
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
module QA
|
||||
RSpec.describe 'Verify', :runner, product_group: :pipeline_security,
|
||||
feature_flag: { name: 'ci_prevent_file_var_expansion_downstream_pipeline', scope: :project },
|
||||
only: { subdomain: 'staging-canary' } do
|
||||
# Runs this test only in staging-canary to debug flakiness https://gitlab.com/gitlab-org/gitlab/-/issues/424903
|
||||
# We need to collect failure data, please don't quarantine for the time being
|
||||
|
|
@ -104,12 +103,6 @@ module QA
|
|||
]
|
||||
end
|
||||
|
||||
around do |example|
|
||||
Runtime::Feature.enable(:ci_prevent_file_var_expansion_downstream_pipeline, project: upstream_project)
|
||||
example.run
|
||||
Runtime::Feature.disable(:ci_prevent_file_var_expansion_downstream_pipeline, project: upstream_project)
|
||||
end
|
||||
|
||||
before do
|
||||
add_file_variables_to_upstream_project
|
||||
add_ci_file(downstream_project, downstream_project_file)
|
||||
|
|
|
|||
|
|
@ -185,17 +185,6 @@ EOF
|
|||
log "success!"
|
||||
}
|
||||
|
||||
function setup_cluster() {
|
||||
local kind_config=$1
|
||||
|
||||
log_with_header "Create kind kubernetes cluster"
|
||||
kind create cluster --config "$kind_config"
|
||||
sed -i -E -e "s/localhost|0\.0\.0\.0/docker/g" "$KUBECONFIG"
|
||||
|
||||
log_with_header "Print cluster info"
|
||||
kubectl cluster-info
|
||||
}
|
||||
|
||||
function deploy() {
|
||||
local domain=$1
|
||||
local values=$(chart_values $domain)
|
||||
|
|
|
|||
|
|
@ -62,6 +62,29 @@ RSpec.describe SearchController, feature_category: :global_search do
|
|||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'internal event tracking' do
|
||||
let(:params) { { search: 'foobar' } }
|
||||
let(:event) { 'perform_search' }
|
||||
let(:category) { described_class.to_s }
|
||||
let(:namespace) { nil }
|
||||
let(:project) { nil }
|
||||
|
||||
subject(:tracked_event) { get :show, params: params }
|
||||
end
|
||||
|
||||
context 'for navbar search' do
|
||||
let(:params) { { search: 'foobar', nav_source: 'navbar' } }
|
||||
let(:category) { described_class.to_s }
|
||||
let(:namespace) { nil }
|
||||
let(:project) { nil }
|
||||
|
||||
it_behaves_like 'internal event tracking' do
|
||||
let(:event) { 'perform_navbar_search' }
|
||||
|
||||
subject(:tracked_event) { get :show, params: params }
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'with external authorization service enabled', :show, { search: 'hello' }
|
||||
it_behaves_like 'support for active record query timeouts', :show, { search: 'hello' }, :search_objects, :html
|
||||
it_behaves_like 'metadata is set', :show
|
||||
|
|
|
|||
|
|
@ -4,9 +4,16 @@ FactoryBot.define do
|
|||
factory :npm_metadata_cache, class: 'Packages::Npm::MetadataCache' do
|
||||
project
|
||||
sequence(:package_name) { |n| "@#{project.root_namespace.path}/package-#{n}" }
|
||||
file { fixture_file_upload('spec/fixtures/packages/npm/metadata.json') }
|
||||
size { 401.bytes }
|
||||
|
||||
transient do
|
||||
file_fixture { 'spec/fixtures/packages/npm/metadata.json' }
|
||||
end
|
||||
|
||||
after(:build) do |entry, evaluator|
|
||||
entry.file = fixture_file_upload(evaluator.file_fixture)
|
||||
end
|
||||
|
||||
trait :processing do
|
||||
status { 'processing' }
|
||||
end
|
||||
|
|
@ -16,5 +23,9 @@ FactoryBot.define do
|
|||
entry.update_attribute(:project_id, nil)
|
||||
end
|
||||
end
|
||||
|
||||
trait(:object_storage) do
|
||||
file_store { Packages::Npm::MetadataCacheUploader::Store::REMOTE }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,16 +3,27 @@
|
|||
FactoryBot.define do
|
||||
factory :nuget_symbol, class: 'Packages::Nuget::Symbol' do
|
||||
package { association(:nuget_package) }
|
||||
file { fixture_file_upload('spec/fixtures/packages/nuget/symbol/package.pdb') }
|
||||
file_path { 'lib/net7.0/package.pdb' }
|
||||
size { 100.bytes }
|
||||
sequence(:signature) { |n| "b91a152048fc4b3883bf3cf73fbc03f#{n}FFFFFFFF" }
|
||||
file_sha256 { 'dd1aaf26c557685cc37f93f53a2b6befb2c2e679f5ace6ec7a26d12086f358be' }
|
||||
|
||||
transient do
|
||||
file_fixture { 'spec/fixtures/packages/nuget/symbol/package.pdb' }
|
||||
end
|
||||
|
||||
after(:build) do |symbol, evaluator|
|
||||
symbol.file = fixture_file_upload(evaluator.file_fixture)
|
||||
end
|
||||
|
||||
trait :stale do
|
||||
after(:create) do |entry|
|
||||
entry.update_attribute(:package_id, nil)
|
||||
end
|
||||
end
|
||||
|
||||
trait(:object_storage) do
|
||||
file_store { Packages::Nuget::SymbolUploader::Store::REMOTE }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,16 +1,17 @@
|
|||
import { GlButton } from '@gitlab/ui';
|
||||
import Vue from 'vue';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import organizationsGraphQlResponse from 'test_fixtures/graphql/organizations/organizations.query.graphql.json';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import { createAlert } from '~/alert';
|
||||
import { DEFAULT_PER_PAGE } from '~/api';
|
||||
import { organizations as nodes, pageInfo, pageInfoEmpty } from '~/organizations/mock_data';
|
||||
import organizationsQuery from '~/admin/organizations/index/graphql/queries/organizations.query.graphql';
|
||||
import OrganizationsIndexApp from '~/admin/organizations/index/components/app.vue';
|
||||
import OrganizationsView from '~/organizations/shared/components/organizations_view.vue';
|
||||
import { MOCK_NEW_ORG_URL } from 'jest/organizations/shared/mock_data';
|
||||
import { pageInfoEmpty } from 'jest/organizations/mock_data';
|
||||
|
||||
jest.mock('~/alert');
|
||||
|
||||
|
|
@ -20,10 +21,11 @@ describe('AdminOrganizationsIndexApp', () => {
|
|||
let wrapper;
|
||||
let mockApollo;
|
||||
|
||||
const organizations = {
|
||||
nodes,
|
||||
pageInfo,
|
||||
};
|
||||
const {
|
||||
data: {
|
||||
currentUser: { organizations },
|
||||
},
|
||||
} = organizationsGraphQlResponse;
|
||||
|
||||
const organizationEmpty = {
|
||||
nodes: [],
|
||||
|
|
|
|||
|
|
@ -26,3 +26,42 @@ RSpec.describe Organizations::GroupsController, '(JavaScript fixtures)', type: :
|
|||
expect(response).to be_successful
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.describe 'Organizations (GraphQL fixtures)', feature_category: :cell do
|
||||
describe GraphQL::Query, type: :request do
|
||||
include GraphqlHelpers
|
||||
include JavaScriptFixturesHelpers
|
||||
|
||||
let_it_be(:current_user) { create(:user) }
|
||||
let_it_be(:organizations) { create_list(:organization, 3) }
|
||||
let_it_be(:organization_users) do
|
||||
organizations.map do |organization|
|
||||
create(:organization_user, organization: organization, user: current_user)
|
||||
end
|
||||
end
|
||||
|
||||
let_it_be(:organization_details) do
|
||||
organizations.map do |organization|
|
||||
create(:organization_detail, organization: organization)
|
||||
end
|
||||
end
|
||||
|
||||
before do
|
||||
sign_in(current_user)
|
||||
end
|
||||
|
||||
describe 'organizations' do
|
||||
base_input_path = 'organizations/shared/graphql/queries/'
|
||||
base_output_path = 'graphql/organizations/'
|
||||
query_name = 'organizations.query.graphql'
|
||||
|
||||
it "#{base_output_path}#{query_name}.json" do
|
||||
query = get_graphql_query_as_string("#{base_input_path}#{query_name}")
|
||||
|
||||
post_graphql(query, current_user: current_user, variables: { search: '', first: 3 })
|
||||
|
||||
expect_graphql_errors_to_be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,15 +1,16 @@
|
|||
import { GlButton } from '@gitlab/ui';
|
||||
import Vue from 'vue';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import organizationsGraphQlResponse from 'test_fixtures/graphql/organizations/organizations.query.graphql.json';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import { createAlert } from '~/alert';
|
||||
import { DEFAULT_PER_PAGE } from '~/api';
|
||||
import { organizations as nodes, pageInfo, pageInfoEmpty } from '~/organizations/mock_data';
|
||||
import organizationsQuery from '~/organizations/shared/graphql/queries/organizations.query.graphql';
|
||||
import OrganizationsIndexApp from '~/organizations/index/components/app.vue';
|
||||
import OrganizationsView from '~/organizations/shared/components/organizations_view.vue';
|
||||
import { pageInfoEmpty } from 'jest/organizations/mock_data';
|
||||
import { MOCK_NEW_ORG_URL } from '../../shared/mock_data';
|
||||
|
||||
jest.mock('~/alert');
|
||||
|
|
@ -20,24 +21,18 @@ describe('OrganizationsIndexApp', () => {
|
|||
let wrapper;
|
||||
let mockApollo;
|
||||
|
||||
const organizations = {
|
||||
nodes,
|
||||
pageInfo,
|
||||
};
|
||||
const {
|
||||
data: {
|
||||
currentUser: { organizations },
|
||||
},
|
||||
} = organizationsGraphQlResponse;
|
||||
|
||||
const organizationEmpty = {
|
||||
nodes: [],
|
||||
pageInfo: pageInfoEmpty,
|
||||
};
|
||||
|
||||
const successHandler = jest.fn().mockResolvedValue({
|
||||
data: {
|
||||
currentUser: {
|
||||
id: 'gid://gitlab/User/1',
|
||||
organizations,
|
||||
},
|
||||
},
|
||||
});
|
||||
const successHandler = jest.fn().mockResolvedValue(organizationsGraphQlResponse);
|
||||
|
||||
const createComponent = (handler = successHandler) => {
|
||||
mockApollo = createMockApollo([[organizationsQuery, handler]]);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
export const pageInfoMultiplePages = {
|
||||
endCursor: 'eyJpZCI6IjEwNTMifQ',
|
||||
hasNextPage: true,
|
||||
hasPreviousPage: true,
|
||||
startCursor: 'eyJpZCI6IjEwNzIifQ',
|
||||
__typename: 'PageInfo',
|
||||
};
|
||||
|
||||
export const pageInfoOnePage = {
|
||||
endCursor: 'eyJpZCI6IjEwNTMifQ',
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
startCursor: 'eyJpZCI6IjEwNzIifQ',
|
||||
__typename: 'PageInfo',
|
||||
};
|
||||
|
||||
export const pageInfoEmpty = {
|
||||
endCursor: null,
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
startCursor: null,
|
||||
__typename: 'PageInfo',
|
||||
};
|
||||
|
|
@ -19,12 +19,12 @@ import { DEFAULT_PER_PAGE } from '~/api';
|
|||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import { organizationGroups as nodes } from '~/organizations/mock_data';
|
||||
import {
|
||||
organizationGroups as nodes,
|
||||
pageInfo,
|
||||
pageInfoMultiplePages,
|
||||
pageInfoEmpty,
|
||||
pageInfoOnePage,
|
||||
} from '~/organizations/mock_data';
|
||||
} from 'jest/organizations/mock_data';
|
||||
|
||||
const MOCK_DELETE_PARAMS = {
|
||||
testParam: true,
|
||||
|
|
@ -59,7 +59,7 @@ describe('GroupsView', () => {
|
|||
|
||||
const groups = {
|
||||
nodes,
|
||||
pageInfo,
|
||||
pageInfo: pageInfoMultiplePages,
|
||||
};
|
||||
|
||||
const successHandler = jest.fn().mockResolvedValue({
|
||||
|
|
@ -203,24 +203,9 @@ describe('GroupsView', () => {
|
|||
|
||||
describe('when there is a next page of groups', () => {
|
||||
const mockEndCursor = 'mockEndCursor';
|
||||
const handler = jest.fn().mockResolvedValue({
|
||||
data: {
|
||||
organization: {
|
||||
id: defaultProvide.organizationGid,
|
||||
groups: {
|
||||
nodes,
|
||||
pageInfo: {
|
||||
...pageInfo,
|
||||
hasNextPage: true,
|
||||
hasPreviousPage: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
createComponent({ handler });
|
||||
createComponent();
|
||||
await waitForPromises();
|
||||
});
|
||||
|
||||
|
|
@ -249,7 +234,7 @@ describe('GroupsView', () => {
|
|||
});
|
||||
|
||||
it('calls query with correct variables', () => {
|
||||
expect(handler).toHaveBeenCalledWith({
|
||||
expect(successHandler).toHaveBeenCalledWith({
|
||||
after: mockEndCursor,
|
||||
before: null,
|
||||
first: DEFAULT_PER_PAGE,
|
||||
|
|
@ -264,24 +249,9 @@ describe('GroupsView', () => {
|
|||
|
||||
describe('when there is a previous page of groups', () => {
|
||||
const mockStartCursor = 'mockStartCursor';
|
||||
const handler = jest.fn().mockResolvedValue({
|
||||
data: {
|
||||
organization: {
|
||||
id: defaultProvide.organizationGid,
|
||||
groups: {
|
||||
nodes,
|
||||
pageInfo: {
|
||||
...pageInfo,
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
createComponent({ handler });
|
||||
createComponent();
|
||||
await waitForPromises();
|
||||
});
|
||||
|
||||
|
|
@ -312,7 +282,7 @@ describe('GroupsView', () => {
|
|||
});
|
||||
|
||||
it('calls query with correct variables', () => {
|
||||
expect(handler).toHaveBeenCalledWith({
|
||||
expect(successHandler).toHaveBeenCalledWith({
|
||||
after: null,
|
||||
before: mockStartCursor,
|
||||
first: null,
|
||||
|
|
|
|||
|
|
@ -1,16 +1,24 @@
|
|||
import { GlAvatarLabeled } from '@gitlab/ui';
|
||||
import organizationsGraphQlResponse from 'test_fixtures/graphql/organizations/organizations.query.graphql.json';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
||||
import OrganizationsListItem from '~/organizations/shared/components/list/organizations_list_item.vue';
|
||||
import { organizations } from '~/organizations/mock_data';
|
||||
|
||||
const MOCK_ORGANIZATION = organizations[0];
|
||||
|
||||
describe('OrganizationsListItem', () => {
|
||||
let wrapper;
|
||||
|
||||
const {
|
||||
data: {
|
||||
currentUser: {
|
||||
organizations: {
|
||||
nodes: [organization],
|
||||
},
|
||||
},
|
||||
},
|
||||
} = organizationsGraphQlResponse;
|
||||
|
||||
const defaultProps = {
|
||||
organization: MOCK_ORGANIZATION,
|
||||
organization,
|
||||
};
|
||||
|
||||
const createComponent = (props = {}) => {
|
||||
|
|
@ -33,11 +41,11 @@ describe('OrganizationsListItem', () => {
|
|||
|
||||
it('renders GlAvatarLabeled with correct data', () => {
|
||||
expect(findGlAvatarLabeled().attributes()).toMatchObject({
|
||||
'entity-id': getIdFromGraphQLId(MOCK_ORGANIZATION.id).toString(),
|
||||
'entity-name': MOCK_ORGANIZATION.name,
|
||||
src: MOCK_ORGANIZATION.avatarUrl,
|
||||
label: MOCK_ORGANIZATION.name,
|
||||
labellink: MOCK_ORGANIZATION.webUrl,
|
||||
'entity-id': getIdFromGraphQLId(organization.id).toString(),
|
||||
'entity-name': organization.name,
|
||||
src: organization.avatarUrl,
|
||||
label: organization.name,
|
||||
labellink: organization.webUrl,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -47,7 +55,7 @@ describe('OrganizationsListItem', () => {
|
|||
|
||||
describe('is a HTML description', () => {
|
||||
beforeEach(() => {
|
||||
createComponent({ organization: { ...MOCK_ORGANIZATION, descriptionHtml } });
|
||||
createComponent({ organization: { ...organization, descriptionHtml } });
|
||||
});
|
||||
|
||||
it('renders HTML description', () => {
|
||||
|
|
@ -58,7 +66,7 @@ describe('OrganizationsListItem', () => {
|
|||
describe('is not a HTML description', () => {
|
||||
beforeEach(() => {
|
||||
createComponent({
|
||||
organization: { ...MOCK_ORGANIZATION, descriptionHtml: null },
|
||||
organization: { ...organization, descriptionHtml: null },
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,20 +1,24 @@
|
|||
import { GlKeysetPagination } from '@gitlab/ui';
|
||||
import { omit } from 'lodash';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import organizationsGraphQlResponse from 'test_fixtures/graphql/organizations/organizations.query.graphql.json';
|
||||
import OrganizationsList from '~/organizations/shared/components/list/organizations_list.vue';
|
||||
import OrganizationsListItem from '~/organizations/shared/components/list/organizations_list_item.vue';
|
||||
import { organizations as nodes, pageInfo, pageInfoOnePage } from '~/organizations/mock_data';
|
||||
import { pageInfoMultiplePages, pageInfoOnePage } from 'jest/organizations/mock_data';
|
||||
|
||||
describe('OrganizationsList', () => {
|
||||
let wrapper;
|
||||
|
||||
const {
|
||||
data: {
|
||||
currentUser: { organizations },
|
||||
},
|
||||
} = organizationsGraphQlResponse;
|
||||
|
||||
const createComponent = ({ propsData = {} } = {}) => {
|
||||
wrapper = shallowMount(OrganizationsList, {
|
||||
propsData: {
|
||||
organizations: {
|
||||
nodes,
|
||||
pageInfo,
|
||||
},
|
||||
organizations,
|
||||
...propsData,
|
||||
},
|
||||
});
|
||||
|
|
@ -27,7 +31,7 @@ describe('OrganizationsList', () => {
|
|||
it('renders a list item for each organization', () => {
|
||||
createComponent();
|
||||
|
||||
expect(findAllOrganizationsListItem()).toHaveLength(nodes.length);
|
||||
expect(findAllOrganizationsListItem()).toHaveLength(organizations.nodes.length);
|
||||
});
|
||||
|
||||
describe('when there is one page of organizations', () => {
|
||||
|
|
@ -35,7 +39,7 @@ describe('OrganizationsList', () => {
|
|||
createComponent({
|
||||
propsData: {
|
||||
organizations: {
|
||||
nodes,
|
||||
...organizations,
|
||||
pageInfo: pageInfoOnePage,
|
||||
},
|
||||
},
|
||||
|
|
@ -49,11 +53,18 @@ describe('OrganizationsList', () => {
|
|||
|
||||
describe('when there are multiple pages of organizations', () => {
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
createComponent({
|
||||
propsData: {
|
||||
organizations: {
|
||||
...organizations,
|
||||
pageInfo: pageInfoMultiplePages,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('renders pagination', () => {
|
||||
expect(findPagination().props()).toMatchObject(omit(pageInfo, '__typename'));
|
||||
expect(findPagination().props()).toMatchObject(omit(pageInfoMultiplePages, '__typename'));
|
||||
});
|
||||
|
||||
describe('when `GlKeysetPagination` emits `next` event', () => {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { GlLoadingIcon, GlEmptyState } from '@gitlab/ui';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import { organizations } from '~/organizations/mock_data';
|
||||
import organizationsGraphQlResponse from 'test_fixtures/graphql/organizations/organizations.query.graphql.json';
|
||||
import OrganizationsView from '~/organizations/shared/components/organizations_view.vue';
|
||||
import OrganizationsList from '~/organizations/shared/components/list/organizations_list.vue';
|
||||
import { MOCK_NEW_ORG_URL, MOCK_ORG_EMPTY_STATE_SVG } from '../mock_data';
|
||||
|
|
@ -8,6 +8,14 @@ import { MOCK_NEW_ORG_URL, MOCK_ORG_EMPTY_STATE_SVG } from '../mock_data';
|
|||
describe('OrganizationsView', () => {
|
||||
let wrapper;
|
||||
|
||||
const {
|
||||
data: {
|
||||
currentUser: {
|
||||
organizations: { nodes: organizations },
|
||||
},
|
||||
},
|
||||
} = organizationsGraphQlResponse;
|
||||
|
||||
const createComponent = (props = {}) => {
|
||||
wrapper = shallowMount(OrganizationsView, {
|
||||
propsData: {
|
||||
|
|
|
|||
|
|
@ -19,12 +19,12 @@ import { DEFAULT_PER_PAGE } from '~/api';
|
|||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import { organizationProjects as nodes } from '~/organizations/mock_data';
|
||||
import {
|
||||
organizationProjects as nodes,
|
||||
pageInfo,
|
||||
pageInfoMultiplePages,
|
||||
pageInfoEmpty,
|
||||
pageInfoOnePage,
|
||||
} from '~/organizations/mock_data';
|
||||
} from 'jest/organizations/mock_data';
|
||||
|
||||
jest.mock('~/alert');
|
||||
jest.mock('~/api/projects_api');
|
||||
|
|
@ -60,7 +60,7 @@ describe('ProjectsView', () => {
|
|||
|
||||
const projects = {
|
||||
nodes,
|
||||
pageInfo,
|
||||
pageInfo: pageInfoMultiplePages,
|
||||
};
|
||||
|
||||
const successHandler = jest.fn().mockResolvedValue({
|
||||
|
|
@ -206,24 +206,9 @@ describe('ProjectsView', () => {
|
|||
|
||||
describe('when there is a next page of projects', () => {
|
||||
const mockEndCursor = 'mockEndCursor';
|
||||
const handler = jest.fn().mockResolvedValue({
|
||||
data: {
|
||||
organization: {
|
||||
id: defaultProvide.organizationGid,
|
||||
projects: {
|
||||
nodes,
|
||||
pageInfo: {
|
||||
...pageInfo,
|
||||
hasNextPage: true,
|
||||
hasPreviousPage: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
createComponent({ handler });
|
||||
createComponent();
|
||||
await waitForPromises();
|
||||
});
|
||||
|
||||
|
|
@ -253,7 +238,7 @@ describe('ProjectsView', () => {
|
|||
});
|
||||
|
||||
it('calls query with correct variables', () => {
|
||||
expect(handler).toHaveBeenCalledWith({
|
||||
expect(successHandler).toHaveBeenCalledWith({
|
||||
after: mockEndCursor,
|
||||
before: null,
|
||||
first: DEFAULT_PER_PAGE,
|
||||
|
|
@ -268,24 +253,9 @@ describe('ProjectsView', () => {
|
|||
|
||||
describe('when there is a previous page of projects', () => {
|
||||
const mockStartCursor = 'mockStartCursor';
|
||||
const handler = jest.fn().mockResolvedValue({
|
||||
data: {
|
||||
organization: {
|
||||
id: defaultProvide.organizationGid,
|
||||
projects: {
|
||||
nodes,
|
||||
pageInfo: {
|
||||
...pageInfo,
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
createComponent({ handler });
|
||||
createComponent();
|
||||
await waitForPromises();
|
||||
});
|
||||
|
||||
|
|
@ -315,7 +285,7 @@ describe('ProjectsView', () => {
|
|||
});
|
||||
|
||||
it('calls query with correct variables', () => {
|
||||
expect(handler).toHaveBeenCalledWith({
|
||||
expect(successHandler).toHaveBeenCalledWith({
|
||||
after: null,
|
||||
before: mockStartCursor,
|
||||
first: null,
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import {
|
|||
colorModes,
|
||||
lightColorModeId,
|
||||
darkColorModeId,
|
||||
autoColorModeId,
|
||||
themes,
|
||||
themeId1,
|
||||
} from '../mock_data';
|
||||
|
|
@ -247,6 +248,20 @@ describe('ProfilePreferences component', () => {
|
|||
|
||||
expect(window.location.reload).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('reloads the page when switching from auto to light mode', async () => {
|
||||
selectColorModeId(autoColorModeId);
|
||||
setupWrapper();
|
||||
|
||||
selectColorModeId(lightColorModeId);
|
||||
dispatchBeforeSendEvent();
|
||||
await nextTick();
|
||||
|
||||
dispatchSuccessEvent();
|
||||
await nextTick();
|
||||
|
||||
expect(window.location.reload).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with extensions marketplace integration view', () => {
|
||||
|
|
|
|||
|
|
@ -21,10 +21,12 @@ export const bodyClasses = 'ui-light-indigo ui-light gl-dark';
|
|||
|
||||
export const lightColorModeId = 1;
|
||||
export const darkColorModeId = 2;
|
||||
export const autoColorModeId = 3;
|
||||
|
||||
export const colorModes = [
|
||||
{ id: lightColorModeId, css_class: 'gl-light' },
|
||||
{ id: darkColorModeId, css_class: 'gl-dark' },
|
||||
{ id: autoColorModeId, css_class: 'gl-system' },
|
||||
];
|
||||
|
||||
export const themes = [
|
||||
|
|
|
|||
|
|
@ -2,14 +2,11 @@ import { GlAvatar, GlDisclosureDropdown, GlLoadingIcon, GlLink } from '@gitlab/u
|
|||
import VueApollo from 'vue-apollo';
|
||||
import Vue from 'vue';
|
||||
|
||||
import organizationsGraphQlResponse from 'test_fixtures/graphql/organizations/organizations.query.graphql.json';
|
||||
import { mountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import OrganizationSwitcher from '~/super_sidebar/components/organization_switcher.vue';
|
||||
import {
|
||||
defaultOrganization as currentOrganization,
|
||||
organizations as nodes,
|
||||
pageInfo,
|
||||
pageInfoEmpty,
|
||||
} from '~/organizations/mock_data';
|
||||
import { defaultOrganization as currentOrganization } from '~/organizations/mock_data';
|
||||
import { pageInfoEmpty } from 'jest/organizations/mock_data';
|
||||
import organizationsQuery from '~/organizations/shared/graphql/queries/organizations.query.graphql';
|
||||
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
||||
import { helpPagePath } from '~/helpers/help_page_helper';
|
||||
|
|
@ -22,7 +19,15 @@ describe('OrganizationSwitcher', () => {
|
|||
let wrapper;
|
||||
let mockApollo;
|
||||
|
||||
const [, secondOrganization, thirdOrganization] = nodes;
|
||||
const {
|
||||
data: {
|
||||
currentUser: {
|
||||
organizations: { nodes, pageInfo },
|
||||
},
|
||||
},
|
||||
} = organizationsGraphQlResponse;
|
||||
|
||||
const [firstOrganization, secondOrganization] = nodes;
|
||||
|
||||
const organizations = {
|
||||
nodes,
|
||||
|
|
@ -97,25 +102,25 @@ describe('OrganizationSwitcher', () => {
|
|||
|
||||
await waitForPromises();
|
||||
|
||||
expect(findDropdownItemByIndex(1).text()).toContain(secondOrganization.name);
|
||||
expect(findDropdownItemByIndex(1).text()).toContain(firstOrganization.name);
|
||||
expect(findDropdownItemByIndex(1).element.firstChild.getAttribute('href')).toBe(
|
||||
secondOrganization.webUrl,
|
||||
firstOrganization.webUrl,
|
||||
);
|
||||
expect(findDropdownItemByIndex(1).findComponent(GlAvatar).props()).toMatchObject({
|
||||
src: firstOrganization.avatarUrl,
|
||||
entityId: getIdFromGraphQLId(firstOrganization.id),
|
||||
entityName: firstOrganization.name,
|
||||
});
|
||||
|
||||
expect(findDropdownItemByIndex(2).text()).toContain(secondOrganization.name);
|
||||
expect(findDropdownItemByIndex(2).element.firstChild.getAttribute('href')).toBe(
|
||||
secondOrganization.webUrl,
|
||||
);
|
||||
expect(findDropdownItemByIndex(2).findComponent(GlAvatar).props()).toMatchObject({
|
||||
src: secondOrganization.avatarUrl,
|
||||
entityId: getIdFromGraphQLId(secondOrganization.id),
|
||||
entityName: secondOrganization.name,
|
||||
});
|
||||
|
||||
expect(findDropdownItemByIndex(2).text()).toContain(thirdOrganization.name);
|
||||
expect(findDropdownItemByIndex(2).element.firstChild.getAttribute('href')).toBe(
|
||||
thirdOrganization.webUrl,
|
||||
);
|
||||
expect(findDropdownItemByIndex(2).findComponent(GlAvatar).props()).toMatchObject({
|
||||
src: thirdOrganization.avatarUrl,
|
||||
entityId: getIdFromGraphQLId(thirdOrganization.id),
|
||||
entityName: thirdOrganization.name,
|
||||
});
|
||||
});
|
||||
|
||||
describe('when there are no organizations to switch to', () => {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import VueApollo from 'vue-apollo';
|
|||
import Vue from 'vue';
|
||||
import { GlCollapsibleListbox, GlAlert } from '@gitlab/ui';
|
||||
import { chunk } from 'lodash';
|
||||
import organizationsGraphQlResponse from 'test_fixtures/graphql/organizations/organizations.query.graphql.json';
|
||||
import { mountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import OrganizationSelect from '~/vue_shared/components/entity_select/organization_select.vue';
|
||||
import EntitySelect from '~/vue_shared/components/entity_select/entity_select.vue';
|
||||
|
|
@ -14,7 +15,7 @@ import {
|
|||
} from '~/vue_shared/components/entity_select/constants';
|
||||
import getCurrentUserOrganizationsQuery from '~/organizations/shared/graphql/queries/organizations.query.graphql';
|
||||
import getOrganizationQuery from '~/organizations/shared/graphql/queries/organization.query.graphql';
|
||||
import { organizations as nodes, pageInfo, pageInfoEmpty } from '~/organizations/mock_data';
|
||||
import { pageInfoMultiplePages, pageInfoEmpty } from 'jest/organizations/mock_data';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
||||
|
|
@ -26,6 +27,13 @@ describe('OrganizationSelect', () => {
|
|||
let mockApollo;
|
||||
|
||||
// Mocks
|
||||
const {
|
||||
data: {
|
||||
currentUser: {
|
||||
organizations: { nodes, pageInfo },
|
||||
},
|
||||
},
|
||||
} = organizationsGraphQlResponse;
|
||||
const [organization] = nodes;
|
||||
const organizations = {
|
||||
nodes,
|
||||
|
|
@ -147,7 +155,7 @@ describe('OrganizationSelect', () => {
|
|||
currentUser: {
|
||||
id: 'gid://gitlab/User/1',
|
||||
__typename: 'CurrentUser',
|
||||
organizations: { nodes: firstPage, pageInfo },
|
||||
organizations: { nodes: firstPage, pageInfo: pageInfoMultiplePages },
|
||||
},
|
||||
},
|
||||
})
|
||||
|
|
@ -178,7 +186,7 @@ describe('OrganizationSelect', () => {
|
|||
it('calls graphQL query correct `after` variable', () => {
|
||||
expect(getCurrentUserOrganizationsQueryMultiplePagesHandler).toHaveBeenCalledWith({
|
||||
search: '',
|
||||
after: pageInfo.endCursor,
|
||||
after: pageInfoMultiplePages.endCursor,
|
||||
first: DEFAULT_PER_PAGE,
|
||||
});
|
||||
expect(findListbox().props('infiniteScroll')).toBe(false);
|
||||
|
|
|
|||
|
|
@ -54,7 +54,6 @@ RSpec.describe Gitlab::Ci::Variables::Downstream::Generator, feature_category: :
|
|||
variables: bridge_variables,
|
||||
forward_yaml_variables?: true,
|
||||
forward_pipeline_variables?: true,
|
||||
expand_file_refs?: false,
|
||||
yaml_variables: yaml_variables,
|
||||
pipeline_variables: pipeline_variables,
|
||||
pipeline_schedule_variables: pipeline_schedule_variables,
|
||||
|
|
@ -125,39 +124,16 @@ RSpec.describe Gitlab::Ci::Variables::Downstream::Generator, feature_category: :
|
|||
[{ key: 'PIPELINE_DOTENV_INTERPOLATION_VAR', value: 'interpolate $REF1 $REF2 $FILE_REF3 $FILE_REF4' }]
|
||||
end
|
||||
|
||||
context 'when expand_file_refs is true' do
|
||||
before do
|
||||
allow(bridge).to receive(:expand_file_refs?).and_return(true)
|
||||
end
|
||||
it 'does not expand file variables and adds file variables' do
|
||||
expected = [
|
||||
{ key: 'INTERPOLATION_VAR', value: 'interpolate ref 1 $FILE_REF3 ' },
|
||||
{ key: 'PIPELINE_INTERPOLATION_VAR', value: 'interpolate ref 1 $FILE_REF3 ' },
|
||||
{ key: 'PIPELINE_SCHEDULE_INTERPOLATION_VAR', value: 'interpolate ref 1 $FILE_REF3 ' },
|
||||
{ key: 'PIPELINE_DOTENV_INTERPOLATION_VAR', value: 'interpolate ref 1 $FILE_REF3 ' },
|
||||
{ key: 'FILE_REF3', value: 'ref 3', variable_type: :file }
|
||||
]
|
||||
|
||||
it 'expands file variables' do
|
||||
expected = [
|
||||
{ key: 'INTERPOLATION_VAR', value: 'interpolate ref 1 ref 3 ' },
|
||||
{ key: 'PIPELINE_INTERPOLATION_VAR', value: 'interpolate ref 1 ref 3 ' },
|
||||
{ key: 'PIPELINE_SCHEDULE_INTERPOLATION_VAR', value: 'interpolate ref 1 ref 3 ' },
|
||||
{ key: 'PIPELINE_DOTENV_INTERPOLATION_VAR', value: 'interpolate ref 1 ref 3 ' }
|
||||
]
|
||||
|
||||
expect(generator.calculate).to contain_exactly(*expected)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when expand_file_refs is false' do
|
||||
before do
|
||||
allow(bridge).to receive(:expand_file_refs?).and_return(false)
|
||||
end
|
||||
|
||||
it 'does not expand file variables and adds file variables' do
|
||||
expected = [
|
||||
{ key: 'INTERPOLATION_VAR', value: 'interpolate ref 1 $FILE_REF3 ' },
|
||||
{ key: 'PIPELINE_INTERPOLATION_VAR', value: 'interpolate ref 1 $FILE_REF3 ' },
|
||||
{ key: 'PIPELINE_SCHEDULE_INTERPOLATION_VAR', value: 'interpolate ref 1 $FILE_REF3 ' },
|
||||
{ key: 'PIPELINE_DOTENV_INTERPOLATION_VAR', value: 'interpolate ref 1 $FILE_REF3 ' },
|
||||
{ key: 'FILE_REF3', value: 'ref 3', variable_type: :file }
|
||||
]
|
||||
|
||||
expect(generator.calculate).to contain_exactly(*expected)
|
||||
end
|
||||
expect(generator.calculate).to contain_exactly(*expected)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -67,18 +67,4 @@ RSpec.describe Gitlab::Usage::Metrics::Instrumentations::RedisMetric, :clean_git
|
|||
time_frame: 'all'
|
||||
}
|
||||
end
|
||||
|
||||
context "with prefix disabled" do
|
||||
let(:expected_value) { 3 }
|
||||
|
||||
before do
|
||||
3.times do
|
||||
Gitlab::UsageDataCounters::SearchCounter.count(:all_searches)
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'a correct instrumented metric value', {
|
||||
options: { event: 'all_searches_count', prefix: nil, include_usage_prefix: false }, time_frame: 'all'
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,31 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::UsageDataCounters::SearchCounter, :clean_gitlab_redis_shared_state do
|
||||
shared_examples_for 'usage counter with totals' do |counter|
|
||||
it 'increments counter and returns total count' do
|
||||
expect(described_class.read(counter)).to eq(0)
|
||||
|
||||
2.times { described_class.count(counter) }
|
||||
|
||||
expect(described_class.read(counter)).to eq(2)
|
||||
end
|
||||
end
|
||||
|
||||
context 'all_searches counter' do
|
||||
it_behaves_like 'usage counter with totals', :all_searches
|
||||
end
|
||||
|
||||
context 'navbar_searches counter' do
|
||||
it_behaves_like 'usage counter with totals', :navbar_searches
|
||||
end
|
||||
|
||||
describe '.fetch_supported_event' do
|
||||
subject { described_class.fetch_supported_event(event_name) }
|
||||
|
||||
let(:event_name) { 'all_searches' }
|
||||
|
||||
it { is_expected.to eq 'all_searches' }
|
||||
end
|
||||
end
|
||||
|
|
@ -343,16 +343,6 @@ RSpec.describe Ci::Bridge, feature_category: :continuous_integration do
|
|||
|
||||
expect(bridge.downstream_variables).to contain_exactly(*expected_vars)
|
||||
end
|
||||
|
||||
context 'and feature flag is disabled' do
|
||||
before do
|
||||
stub_feature_flags(ci_prevent_file_var_expansion_downstream_pipeline: false)
|
||||
end
|
||||
|
||||
it 'expands the file variable' do
|
||||
expect(bridge.downstream_variables).to contain_exactly({ key: 'EXPANDED_FILE', value: 'test-file-value' })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when recursive interpolation has been used' do
|
||||
|
|
@ -455,21 +445,6 @@ RSpec.describe Ci::Bridge, feature_category: :continuous_integration do
|
|||
|
||||
expect(bridge.downstream_variables).to contain_exactly(*expected_vars)
|
||||
end
|
||||
|
||||
context 'and feature flag is disabled' do
|
||||
before do
|
||||
stub_feature_flags(ci_prevent_file_var_expansion_downstream_pipeline: false)
|
||||
end
|
||||
|
||||
it 'expands the file variable' do
|
||||
expected_vars = [
|
||||
{ key: 'FILE_VAR', value: 'project file' },
|
||||
{ key: 'YAML_VAR', value: 'project file' }
|
||||
]
|
||||
|
||||
expect(bridge.downstream_variables).to contain_exactly(*expected_vars)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the pipeline runs from a pipeline schedule' do
|
||||
|
|
@ -537,16 +512,6 @@ RSpec.describe Ci::Bridge, feature_category: :continuous_integration do
|
|||
|
||||
expect(bridge.downstream_variables).to contain_exactly(*expected_vars)
|
||||
end
|
||||
|
||||
context 'and feature flag is disabled' do
|
||||
before do
|
||||
stub_feature_flags(ci_prevent_file_var_expansion_downstream_pipeline: false)
|
||||
end
|
||||
|
||||
it 'expands the file variable' do
|
||||
expect(bridge.downstream_variables).to contain_exactly({ key: 'schedule_var_key', value: 'project file' })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when using raw variables' do
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Packages::Composer::CacheFile, type: :model do
|
||||
RSpec.describe Packages::Composer::CacheFile, type: :model, feature_category: :package_registry do
|
||||
describe 'relationships' do
|
||||
it { is_expected.to belong_to(:group) }
|
||||
it { is_expected.to belong_to(:namespace) }
|
||||
|
|
|
|||
|
|
@ -2,6 +2,6 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Packages::Debian::GroupComponentFile do
|
||||
RSpec.describe Packages::Debian::GroupComponentFile, feature_category: :package_registry do
|
||||
it_behaves_like 'Debian Component File', :group, false
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,6 +2,6 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Packages::Debian::GroupComponent do
|
||||
RSpec.describe Packages::Debian::GroupComponent, feature_category: :package_registry do
|
||||
it_behaves_like 'Debian Distribution Component', :debian_group_component, :group, false
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,6 +2,6 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Packages::Debian::ProjectComponentFile do
|
||||
RSpec.describe Packages::Debian::ProjectComponentFile, feature_category: :package_registry do
|
||||
it_behaves_like 'Debian Component File', :project, true
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,6 +2,6 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Packages::Debian::ProjectComponent do
|
||||
RSpec.describe Packages::Debian::ProjectComponent, feature_category: :package_registry do
|
||||
it_behaves_like 'Debian Distribution Component', :debian_project_component, :project, true
|
||||
end
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ RSpec.describe Packages::Nuget::Symbol, type: :model, feature_category: :package
|
|||
|
||||
describe 'delegations' do
|
||||
it { is_expected.to delegate_method(:project_id).to(:package) }
|
||||
it { is_expected.to delegate_method(:project).to(:package) }
|
||||
end
|
||||
|
||||
describe 'scopes' do
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Packages::PackageFile, type: :model do
|
||||
RSpec.describe Packages::PackageFile, type: :model, feature_category: :package_registry do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
let_it_be(:project) { create(:project) }
|
||||
|
|
|
|||
|
|
@ -19,14 +19,6 @@ RSpec.describe API::Search, :clean_gitlab_redis_rate_limiting, feature_category:
|
|||
it { expect(json_response.size).to eq(size) }
|
||||
end
|
||||
|
||||
shared_examples 'ping counters' do |scope:, search: ''|
|
||||
it 'increases usage ping searches counter' do
|
||||
expect(Gitlab::UsageDataCounters::SearchCounter).to receive(:count).with(:all_searches)
|
||||
|
||||
get api(endpoint, user), params: { scope: scope, search: search }
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'apdex recorded' do |scope:, level:, search: ''|
|
||||
it 'increments the custom search sli apdex' do
|
||||
expect(Gitlab::Metrics::GlobalSearchSlis).to receive(:record_apdex).with(
|
||||
|
|
@ -190,6 +182,21 @@ RSpec.describe API::Search, :clean_gitlab_redis_rate_limiting, feature_category:
|
|||
end
|
||||
|
||||
context 'with correct params' do
|
||||
[:issues, :merge_requests, :projects, :milestones, :users, :snippet_titles].each do |scope|
|
||||
context "with correct params for scope #{scope}" do
|
||||
it_behaves_like 'internal event tracking' do
|
||||
let(:event) { 'perform_search' }
|
||||
let(:category) { described_class.to_s }
|
||||
let(:project) { nil }
|
||||
let(:namespace) { nil }
|
||||
|
||||
subject(:tracked_event) do
|
||||
get api(endpoint, user), params: { scope: scope, search: 'foobar' }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'for projects scope' do
|
||||
before do
|
||||
get api(endpoint, user), params: { scope: 'projects', search: 'awesome' }
|
||||
|
|
@ -199,8 +206,6 @@ RSpec.describe API::Search, :clean_gitlab_redis_rate_limiting, feature_category:
|
|||
|
||||
it_behaves_like 'pagination', scope: :projects
|
||||
|
||||
it_behaves_like 'ping counters', scope: :projects
|
||||
|
||||
it_behaves_like 'apdex recorded', scope: 'projects', level: 'global'
|
||||
end
|
||||
|
||||
|
|
@ -214,8 +219,6 @@ RSpec.describe API::Search, :clean_gitlab_redis_rate_limiting, feature_category:
|
|||
|
||||
it_behaves_like 'response is correct', schema: 'public_api/v4/issues'
|
||||
|
||||
it_behaves_like 'ping counters', scope: :issues
|
||||
|
||||
it_behaves_like 'apdex recorded', scope: 'issues', level: 'global'
|
||||
|
||||
it_behaves_like 'issues orderable by created_at'
|
||||
|
|
@ -278,8 +281,6 @@ RSpec.describe API::Search, :clean_gitlab_redis_rate_limiting, feature_category:
|
|||
|
||||
it_behaves_like 'response is correct', schema: 'public_api/v4/merge_requests'
|
||||
|
||||
it_behaves_like 'ping counters', scope: :merge_requests
|
||||
|
||||
it_behaves_like 'apdex recorded', scope: 'merge_requests', level: 'global'
|
||||
|
||||
it_behaves_like 'merge_requests orderable by created_at'
|
||||
|
|
@ -325,8 +326,6 @@ RSpec.describe API::Search, :clean_gitlab_redis_rate_limiting, feature_category:
|
|||
|
||||
it_behaves_like 'response is correct', schema: 'public_api/v4/milestones'
|
||||
|
||||
it_behaves_like 'ping counters', scope: :milestones
|
||||
|
||||
it_behaves_like 'apdex recorded', scope: 'milestones', level: 'global'
|
||||
|
||||
describe 'pagination' do
|
||||
|
|
@ -365,8 +364,6 @@ RSpec.describe API::Search, :clean_gitlab_redis_rate_limiting, feature_category:
|
|||
|
||||
it_behaves_like 'pagination', scope: :users
|
||||
|
||||
it_behaves_like 'ping counters', scope: :users
|
||||
|
||||
it_behaves_like 'apdex recorded', scope: 'users', level: 'global'
|
||||
end
|
||||
|
||||
|
|
@ -379,8 +376,6 @@ RSpec.describe API::Search, :clean_gitlab_redis_rate_limiting, feature_category:
|
|||
|
||||
it_behaves_like 'response is correct', schema: 'public_api/v4/snippets'
|
||||
|
||||
it_behaves_like 'ping counters', scope: :snippet_titles
|
||||
|
||||
it_behaves_like 'apdex recorded', scope: 'snippet_titles', level: 'global'
|
||||
|
||||
describe 'pagination' do
|
||||
|
|
@ -545,8 +540,6 @@ RSpec.describe API::Search, :clean_gitlab_redis_rate_limiting, feature_category:
|
|||
|
||||
it_behaves_like 'pagination', scope: :projects
|
||||
|
||||
it_behaves_like 'ping counters', scope: :projects
|
||||
|
||||
it_behaves_like 'apdex recorded', scope: 'projects', level: 'group'
|
||||
end
|
||||
|
||||
|
|
@ -559,8 +552,6 @@ RSpec.describe API::Search, :clean_gitlab_redis_rate_limiting, feature_category:
|
|||
|
||||
it_behaves_like 'response is correct', schema: 'public_api/v4/issues'
|
||||
|
||||
it_behaves_like 'ping counters', scope: :issues
|
||||
|
||||
it_behaves_like 'apdex recorded', scope: 'issues', level: 'group'
|
||||
|
||||
it_behaves_like 'issues orderable by created_at'
|
||||
|
|
@ -583,8 +574,6 @@ RSpec.describe API::Search, :clean_gitlab_redis_rate_limiting, feature_category:
|
|||
|
||||
it_behaves_like 'response is correct', schema: 'public_api/v4/merge_requests'
|
||||
|
||||
it_behaves_like 'ping counters', scope: :merge_requests
|
||||
|
||||
it_behaves_like 'apdex recorded', scope: 'merge_requests', level: 'group'
|
||||
|
||||
it_behaves_like 'merge_requests orderable by created_at'
|
||||
|
|
@ -607,8 +596,6 @@ RSpec.describe API::Search, :clean_gitlab_redis_rate_limiting, feature_category:
|
|||
|
||||
it_behaves_like 'response is correct', schema: 'public_api/v4/milestones'
|
||||
|
||||
it_behaves_like 'ping counters', scope: :milestones
|
||||
|
||||
it_behaves_like 'apdex recorded', scope: 'milestones', level: 'group'
|
||||
|
||||
describe 'pagination' do
|
||||
|
|
@ -642,8 +629,6 @@ RSpec.describe API::Search, :clean_gitlab_redis_rate_limiting, feature_category:
|
|||
|
||||
it_behaves_like 'response is correct', schema: 'public_api/v4/user/basics'
|
||||
|
||||
it_behaves_like 'ping counters', scope: :users
|
||||
|
||||
it_behaves_like 'apdex recorded', scope: 'users', level: 'group'
|
||||
|
||||
describe 'pagination' do
|
||||
|
|
@ -756,8 +741,6 @@ RSpec.describe API::Search, :clean_gitlab_redis_rate_limiting, feature_category:
|
|||
|
||||
it_behaves_like 'response is correct', schema: 'public_api/v4/issues'
|
||||
|
||||
it_behaves_like 'ping counters', scope: :issues
|
||||
|
||||
it_behaves_like 'issues orderable by created_at'
|
||||
|
||||
it_behaves_like 'apdex recorded', scope: 'issues', level: 'project'
|
||||
|
|
@ -790,8 +773,6 @@ RSpec.describe API::Search, :clean_gitlab_redis_rate_limiting, feature_category:
|
|||
|
||||
it_behaves_like 'response is correct', schema: 'public_api/v4/merge_requests'
|
||||
|
||||
it_behaves_like 'ping counters', scope: :merge_requests
|
||||
|
||||
it_behaves_like 'merge_requests orderable by created_at'
|
||||
|
||||
it_behaves_like 'apdex recorded', scope: 'merge_requests', level: 'project'
|
||||
|
|
@ -817,8 +798,6 @@ RSpec.describe API::Search, :clean_gitlab_redis_rate_limiting, feature_category:
|
|||
|
||||
it_behaves_like 'response is correct', schema: 'public_api/v4/milestones'
|
||||
|
||||
it_behaves_like 'ping counters', scope: :milestones
|
||||
|
||||
it_behaves_like 'apdex recorded', scope: 'milestones', level: 'project'
|
||||
|
||||
describe 'pagination' do
|
||||
|
|
@ -856,8 +835,6 @@ RSpec.describe API::Search, :clean_gitlab_redis_rate_limiting, feature_category:
|
|||
|
||||
it_behaves_like 'response is correct', schema: 'public_api/v4/user/basics'
|
||||
|
||||
it_behaves_like 'ping counters', scope: :users
|
||||
|
||||
it_behaves_like 'apdex recorded', scope: 'users', level: 'project'
|
||||
|
||||
describe 'pagination' do
|
||||
|
|
@ -878,8 +855,6 @@ RSpec.describe API::Search, :clean_gitlab_redis_rate_limiting, feature_category:
|
|||
|
||||
it_behaves_like 'response is correct', schema: 'public_api/v4/notes'
|
||||
|
||||
it_behaves_like 'ping counters', scope: :notes
|
||||
|
||||
it_behaves_like 'apdex recorded', scope: 'notes', level: 'project'
|
||||
|
||||
describe 'pagination' do
|
||||
|
|
@ -903,8 +878,6 @@ RSpec.describe API::Search, :clean_gitlab_redis_rate_limiting, feature_category:
|
|||
|
||||
it_behaves_like 'response is correct', schema: 'public_api/v4/wiki_blobs'
|
||||
|
||||
it_behaves_like 'ping counters', scope: :wiki_blobs
|
||||
|
||||
it_behaves_like 'apdex recorded', scope: 'wiki_blobs', level: 'project'
|
||||
|
||||
describe 'pagination' do
|
||||
|
|
@ -927,8 +900,6 @@ RSpec.describe API::Search, :clean_gitlab_redis_rate_limiting, feature_category:
|
|||
|
||||
it_behaves_like 'pagination', scope: :commits, search: 'merge'
|
||||
|
||||
it_behaves_like 'ping counters', scope: :commits
|
||||
|
||||
it_behaves_like 'apdex recorded', scope: 'commits', level: 'project'
|
||||
|
||||
describe 'pipeline visibility' do
|
||||
|
|
@ -1043,8 +1014,6 @@ RSpec.describe API::Search, :clean_gitlab_redis_rate_limiting, feature_category:
|
|||
|
||||
it_behaves_like 'pagination', scope: :blobs, search: 'monitors'
|
||||
|
||||
it_behaves_like 'ping counters', scope: :blobs
|
||||
|
||||
it_behaves_like 'apdex recorded', scope: 'blobs', level: 'project'
|
||||
|
||||
context 'filters' do
|
||||
|
|
|
|||
|
|
@ -6617,7 +6617,6 @@
|
|||
- './spec/lib/gitlab/usage_data_counters/package_event_counter_spec.rb'
|
||||
- './spec/lib/gitlab/usage_data_counters/quick_action_activity_unique_counter_spec.rb'
|
||||
- './spec/lib/gitlab/usage_data_counters/redis_counter_spec.rb'
|
||||
- './spec/lib/gitlab/usage_data_counters/search_counter_spec.rb'
|
||||
- './spec/lib/gitlab/usage_data_counters/source_code_counter_spec.rb'
|
||||
- './spec/lib/gitlab/usage_data_counters_spec.rb'
|
||||
- './spec/lib/gitlab/usage_data_counters/vscode_extension_activity_unique_counter_spec.rb'
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue