Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2024-05-10 09:18:47 +00:00
parent 8b6c0125cb
commit 1efcb3739b
110 changed files with 1353 additions and 835 deletions

View File

@ -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'"

View File

@ -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'

View File

@ -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'

View File

@ -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'

View File

@ -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

View File

@ -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',
};

View File

@ -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();

View File

@ -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"

View File

@ -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)

View File

@ -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

View File

@ -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?

View File

@ -14,6 +14,8 @@ module Packages
inverse_of: :component,
dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
delegate container_type, to: :distribution
validates :distribution,
presence: true

View File

@ -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 }

View File

@ -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 }

View File

@ -1,6 +1,7 @@
# frozen_string_literal: true
class Packages::Composer::CacheUploader < GitlabUploader
include ObjectStorage::Concern
include Packages::GcsSignedUrlMetadata
storage_location :packages

View File

@ -2,6 +2,7 @@
class Packages::Debian::ComponentFileUploader < GitlabUploader
extend Workhorse::UploadPath
include ObjectStorage::Concern
include Packages::GcsSignedUrlMetadata
storage_location :packages

View File

@ -2,6 +2,7 @@
class Packages::Debian::DistributionReleaseFileUploader < GitlabUploader
extend Workhorse::UploadPath
include ObjectStorage::Concern
include Packages::GcsSignedUrlMetadata
storage_location :packages

View File

@ -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

View File

@ -4,6 +4,7 @@ module Packages
module Npm
class MetadataCacheUploader < GitlabUploader
include ObjectStorage::Concern
include Packages::GcsSignedUrlMetadata
FILENAME = 'metadata.json'

View File

@ -4,6 +4,7 @@ module Packages
module Nuget
class SymbolUploader < GitlabUploader
include ObjectStorage::Concern
include Packages::GcsSignedUrlMetadata
storage_location :packages

View File

@ -2,6 +2,7 @@
class Packages::PackageFileUploader < GitlabUploader
extend Workhorse::UploadPath
include ObjectStorage::Concern
include Packages::GcsSignedUrlMetadata
storage_location :packages

View File

@ -3,6 +3,7 @@ module Packages
module Rpm
class RepositoryFileUploader < GitlabUploader
include ObjectStorage::Concern
include Packages::GcsSignedUrlMetadata
storage_location :packages

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -533,6 +533,8 @@ ldapsearch
Lefthook
Leiningen
Lemmy
LLM
LLMs
libFuzzer
Libgcrypt
Libravatar

View File

@ -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

76
doc/user/ai_data_usage.md Normal file
View File

@ -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.

View File

@ -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.

View File

@ -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).

View File

@ -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

View File

@ -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 -->

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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!

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -9,7 +9,6 @@ module Gitlab
DiffsCounter,
KubernetesAgentCounter,
NoteCounter,
SearchCounter,
WebIdeCounter,
SourceCodeCounter,
MergeRequestWidgetExtensionCounter

View File

@ -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

View File

@ -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

View File

@ -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."

View File

@ -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)

View 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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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: [],

View File

@ -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

View File

@ -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]]);

View File

@ -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',
};

View File

@ -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,

View File

@ -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 },
});
});

View File

@ -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', () => {

View File

@ -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: {

View File

@ -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,

View File

@ -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', () => {

View File

@ -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 = [

View File

@ -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', () => {

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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) }

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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) }

View File

@ -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

View File

@ -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