Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
05cde74f48
commit
a1ee5ac49b
2
Gemfile
2
Gemfile
|
@ -489,7 +489,7 @@ gem 'flipper', '~> 0.21.0'
|
|||
gem 'flipper-active_record', '~> 0.21.0'
|
||||
gem 'flipper-active_support_cache_store', '~> 0.21.0'
|
||||
gem 'unleash', '~> 3.2.2'
|
||||
gem 'gitlab-experiment', '~> 0.6.5'
|
||||
gem 'gitlab-experiment', '~> 0.7.0'
|
||||
|
||||
# Structured logging
|
||||
gem 'lograge', '~> 0.5'
|
||||
|
|
12
Gemfile.lock
12
Gemfile.lock
|
@ -460,10 +460,9 @@ GEM
|
|||
gitlab-dangerfiles (2.8.0)
|
||||
danger (>= 8.3.1)
|
||||
danger-gitlab (>= 8.0.0)
|
||||
gitlab-experiment (0.6.5)
|
||||
gitlab-experiment (0.7.0)
|
||||
activesupport (>= 3.0)
|
||||
request_store (>= 1.0)
|
||||
scientist (~> 1.6, >= 1.6.0)
|
||||
gitlab-fog-azure-rm (1.2.0)
|
||||
azure-storage-blob (~> 2.0)
|
||||
azure-storage-common (~> 2.0)
|
||||
|
@ -640,7 +639,7 @@ GEM
|
|||
mime-types (~> 3.0)
|
||||
multi_xml (>= 0.5.2)
|
||||
httpclient (2.8.3)
|
||||
i18n (1.8.11)
|
||||
i18n (1.9.1)
|
||||
concurrent-ruby (~> 1.0)
|
||||
i18n_data (0.8.0)
|
||||
icalendar (2.4.1)
|
||||
|
@ -1035,7 +1034,7 @@ GEM
|
|||
declarative (< 0.1.0)
|
||||
declarative-option (< 0.2.0)
|
||||
uber (< 0.2.0)
|
||||
request_store (1.5.0)
|
||||
request_store (1.5.1)
|
||||
rack (>= 1.4)
|
||||
responders (3.0.0)
|
||||
actionpack (>= 5.0)
|
||||
|
@ -1157,7 +1156,6 @@ GEM
|
|||
sawyer (0.8.2)
|
||||
addressable (>= 2.3.5)
|
||||
faraday (> 0.8, < 2.0)
|
||||
scientist (1.6.2)
|
||||
sd_notify (0.1.0)
|
||||
securecompare (1.0.0)
|
||||
seed-fu (2.3.7)
|
||||
|
@ -1377,7 +1375,7 @@ GEM
|
|||
nokogiri (~> 1.8)
|
||||
yajl-ruby (1.4.1)
|
||||
yard (0.9.26)
|
||||
zeitwerk (2.5.3)
|
||||
zeitwerk (2.5.4)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
@ -1470,7 +1468,7 @@ DEPENDENCIES
|
|||
github-markup (~> 1.7.0)
|
||||
gitlab-chronic (~> 0.10.5)
|
||||
gitlab-dangerfiles (~> 2.8.0)
|
||||
gitlab-experiment (~> 0.6.5)
|
||||
gitlab-experiment (~> 0.7.0)
|
||||
gitlab-fog-azure-rm (~> 1.2.0)
|
||||
gitlab-labkit (~> 0.21.3)
|
||||
gitlab-license (~> 2.1.0)
|
||||
|
|
|
@ -9,7 +9,13 @@ export default () => {
|
|||
return null;
|
||||
}
|
||||
|
||||
const { activityEmptyStateImage, agentName, emptyStateSvgPath, projectPath } = el.dataset;
|
||||
const {
|
||||
activityEmptyStateImage,
|
||||
agentName,
|
||||
canAdminVulnerability,
|
||||
emptyStateSvgPath,
|
||||
projectPath,
|
||||
} = el.dataset;
|
||||
|
||||
return new Vue({
|
||||
el,
|
||||
|
@ -17,6 +23,7 @@ export default () => {
|
|||
provide: {
|
||||
activityEmptyStateImage,
|
||||
agentName,
|
||||
canAdminVulnerability,
|
||||
emptyStateSvgPath,
|
||||
projectPath,
|
||||
},
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { convertToSnakeCase, convertToCamelCase } from '~/lib/utils/text_utility';
|
||||
import { DEFAULT_TH_CLASSES } from './constants';
|
||||
|
||||
/**
|
||||
|
@ -7,3 +8,37 @@ import { DEFAULT_TH_CLASSES } from './constants';
|
|||
* @returns {String} The classes to be used in GlTable fields object.
|
||||
*/
|
||||
export const thWidthClass = (width) => `gl-w-${width}p ${DEFAULT_TH_CLASSES}`;
|
||||
|
||||
/**
|
||||
* Converts a GlTable sort-changed event object into string format.
|
||||
* This string can be used as a sort argument on GraphQL queries.
|
||||
*
|
||||
* @param {Object} - The table state context object.
|
||||
* @returns {String} A string with the sort key and direction, for example 'NAME_DESC'.
|
||||
*/
|
||||
export const sortObjectToString = ({ sortBy, sortDesc }) => {
|
||||
const sortingDirection = sortDesc ? 'DESC' : 'ASC';
|
||||
const sortingColumn = convertToSnakeCase(sortBy).toUpperCase();
|
||||
|
||||
return `${sortingColumn}_${sortingDirection}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts a sort string into a sort state object that can be used to
|
||||
* set the sort order on GlTable.
|
||||
*
|
||||
* @param {String} - The string with the sort key and direction, for example 'NAME_DESC'.
|
||||
* @returns {Object} An object with the sortBy and sortDesc properties.
|
||||
*/
|
||||
export const sortStringToObject = (sortString) => {
|
||||
let sortBy = null;
|
||||
let sortDesc = null;
|
||||
|
||||
if (sortString && sortString.includes('_')) {
|
||||
const [key, direction] = sortString.split(/_(ASC|DESC)$/);
|
||||
sortBy = convertToCamelCase(key.toLowerCase());
|
||||
sortDesc = direction === 'DESC';
|
||||
}
|
||||
|
||||
return { sortBy, sortDesc };
|
||||
};
|
||||
|
|
|
@ -18,8 +18,6 @@ export function expandSection(sectionArg) {
|
|||
const $section = $(sectionArg);
|
||||
|
||||
$section.find('.js-settings-toggle:not(.js-settings-toggle-trigger-only)').text(__('Collapse'));
|
||||
// eslint-disable-next-line @gitlab/no-global-event-off
|
||||
$section.find('.settings-content').off('scroll.expandSection').scrollTop(0);
|
||||
$section.addClass('expanded');
|
||||
if (!$section.hasClass('no-animate')) {
|
||||
$section
|
||||
|
@ -32,7 +30,6 @@ export function closeSection(sectionArg) {
|
|||
const $section = $(sectionArg);
|
||||
|
||||
$section.find('.js-settings-toggle:not(.js-settings-toggle-trigger-only)').text(__('Expand'));
|
||||
$section.find('.settings-content').on('scroll.expandSection', () => expandSection($section));
|
||||
$section.removeClass('expanded');
|
||||
if (!$section.hasClass('no-animate')) {
|
||||
$section
|
||||
|
@ -55,18 +52,16 @@ export default function initSettingsPanels() {
|
|||
const $section = $(elm);
|
||||
$section.on('click.toggleSection', '.js-settings-toggle', () => toggleSection($section));
|
||||
|
||||
if (!isExpanded($section)) {
|
||||
$section.find('.settings-content').on('scroll.expandSection', () => {
|
||||
$section.removeClass('no-animate');
|
||||
if (window.location.hash) {
|
||||
const $target = $(window.location.hash);
|
||||
if (
|
||||
$target.length &&
|
||||
!isExpanded($section) &&
|
||||
($section.is($target) || $section.find($target).length)
|
||||
) {
|
||||
$section.addClass('no-animate');
|
||||
expandSection($section);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (window.location.hash) {
|
||||
const $target = $(window.location.hash);
|
||||
if ($target.length && $target.hasClass('settings')) {
|
||||
expandSection($target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,10 +41,6 @@ class ApplicationExperiment < Gitlab::Experiment # rubocop:disable Gitlab/Namesp
|
|||
# define a default nil control behavior so we can omit it when not needed
|
||||
end
|
||||
|
||||
def track(action, **event_args)
|
||||
super(action, **tracking_context.merge(event_args))
|
||||
end
|
||||
|
||||
# TODO: remove
|
||||
# This is deprecated logic as of v0.6.0 and should eventually be removed, but
|
||||
# needs to stay intact for actively running experiments. The new strategy
|
||||
|
@ -64,12 +60,12 @@ class ApplicationExperiment < Gitlab::Experiment # rubocop:disable Gitlab/Namesp
|
|||
|
||||
private
|
||||
|
||||
def tracking_context
|
||||
def tracking_context(event_args)
|
||||
{
|
||||
namespace: context.try(:namespace) || context.try(:group),
|
||||
project: context.try(:project),
|
||||
user: user_or_actor
|
||||
}.compact || {}
|
||||
}.merge(event_args)
|
||||
end
|
||||
|
||||
def user_or_actor
|
||||
|
|
|
@ -5,6 +5,7 @@ module Projects::ClusterAgentsHelper
|
|||
{
|
||||
activity_empty_state_image: image_path('illustrations/empty-state/empty-state-agents.svg'),
|
||||
agent_name: agent_name,
|
||||
can_admin_vulnerability: can?(current_user, :admin_vulnerability, project).to_s,
|
||||
empty_state_svg_path: image_path('illustrations/operations-dashboard_empty.svg'),
|
||||
project_path: project.full_path
|
||||
}
|
||||
|
|
|
@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/330039
|
|||
milestone: '13.12'
|
||||
type: development
|
||||
group: group::import
|
||||
default_enabled: false
|
||||
default_enabled: true
|
||||
|
|
|
@ -10,6 +10,13 @@ Gitlab::Experiment.configure do |config|
|
|||
#
|
||||
config.base_class = 'ApplicationExperiment'
|
||||
|
||||
# Customize the logic of our default rollout, which shouldn't include
|
||||
# assigning the control yet -- we specifically set it to false for now.
|
||||
#
|
||||
config.default_rollout = Gitlab::Experiment::Rollout::Percent.new(
|
||||
include_control: false
|
||||
)
|
||||
|
||||
# Mount the engine and middleware at a gitlab friendly style path.
|
||||
#
|
||||
# The middleware currently focuses only on handling redirection logic, which
|
||||
|
|
|
@ -190,7 +190,7 @@ As an administrator, you can modify the maximum import file size. To do so, use
|
|||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/282503) in GitLab 13.12 in [Beta](https://about.gitlab.com/handbook/product/gitlab-the-product/#beta).
|
||||
|
||||
This endpoint is behind a feature flag that is disabled by default.
|
||||
This endpoint is behind a feature flag that is enabled by default.
|
||||
|
||||
To enable this endpoint:
|
||||
|
||||
|
|
|
@ -450,3 +450,26 @@ To configure access for `<aws_account_id>.dkr.ecr.<region>.amazonaws.com`, follo
|
|||
|
||||
You can add configuration for as many registries as you want, adding more
|
||||
registries to the `"credHelpers"` hash.
|
||||
|
||||
### Use checksum to keep your image secure
|
||||
|
||||
We recommend using the image checksum in your job definition in your `.gitlab-ci.yml` file to verify the integrity of the image. A failed image integrity verification will prevent you from using a modified container.
|
||||
|
||||
To use the image checksum you have to append the checksum at the end:
|
||||
|
||||
```yaml
|
||||
image: ruby:2.6.8@sha256:d1dbaf9665fe8b2175198e49438092fdbcf4d8934200942b94425301b17853c7
|
||||
```
|
||||
|
||||
To get the image checksum, on the image `TAG` tab, view the `DIGEST` column.
|
||||
For example, view the [Ruby image](https://hub.docker.com/_/ruby?tab=tags).
|
||||
The checksum is a random string, like `6155f0235e95`.
|
||||
|
||||
You can also get the checksum of any image on your system with the command `docker images --digests`:
|
||||
|
||||
```shell
|
||||
❯ docker images --digests
|
||||
REPOSITORY TAG DIGEST (...)
|
||||
gitlab/gitlab-ee latest sha256:723aa6edd8f122d50cae490b1743a616d54d4a910db892314d68470cc39dfb24 (...)
|
||||
gitlab/gitlab-runner latest sha256:4a18a80f5be5df44cb7575f6b89d1fdda343297c6fd666c015c0e778b276e726 (...)
|
||||
```
|
||||
|
|
|
@ -4,26 +4,24 @@ group: Adoption
|
|||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
# Implementing an A/B/n experiment using GLEX
|
||||
# Implementing an A/B/n experiment
|
||||
|
||||
## Introduction
|
||||
|
||||
`Gitlab::Experiment` (GLEX) is tightly coupled with the concepts provided by
|
||||
[Feature flags in development of GitLab](../feature_flags/index.md). Here, we refer
|
||||
to this layer as feature flags, and may also use the term Flipper, because we
|
||||
built our development and experiment feature flags atop it.
|
||||
Experiments in GitLab are tightly coupled with the concepts provided by
|
||||
[Feature flags in development of GitLab](../feature_flags/index.md). You're strongly encouraged
|
||||
to read and understand the [Feature flags in development of GitLab](../feature_flags/index.md)
|
||||
portion of the documentation before considering running experiments. Experiments add additional
|
||||
concepts which may seem confusing or advanced without understanding the underpinnings of how GitLab
|
||||
uses feature flags in development. One concept: experiments can be run with multiple variants,
|
||||
which are sometimes referred to as A/B/n tests.
|
||||
|
||||
You're strongly encouraged to read and understand the
|
||||
[Feature flags in development of GitLab](../feature_flags/index.md) portion of the
|
||||
documentation before considering running experiments. Experiments add additional
|
||||
concepts which may seem confusing or advanced without understanding the underpinnings
|
||||
of how GitLab uses feature flags in development. One concept: GLEX supports
|
||||
experiments with multiple variants, which are sometimes referred to as A/B/n tests.
|
||||
|
||||
The [`gitlab-experiment` project](https://gitlab.com/gitlab-org/ruby/gems/gitlab-experiment)
|
||||
exists in a separate repository, so it can be shared across any GitLab property that uses
|
||||
Ruby. You should feel comfortable reading the documentation on that project as well
|
||||
if you want to dig into more advanced topics.
|
||||
We use the [`gitlab-experiment` gem](https://gitlab.com/gitlab-org/ruby/gems/gitlab-experiment),
|
||||
sometimes referred to as GLEX, to run our experiments. The gem exists in a separate repository
|
||||
so it can be shared across any GitLab property that uses Ruby. You should feel comfortable reading
|
||||
the documentation on that project if you want to dig into more advanced topics or open issues. Be
|
||||
aware that the documentation there reflects what's in the main branch and may not be the same as
|
||||
the version being used within GitLab.
|
||||
|
||||
## Glossary of terms
|
||||
|
||||
|
@ -35,41 +33,9 @@ when communicating about experiments:
|
|||
- `control`: The default, or "original" code path.
|
||||
- `candidate`: Defines an experiment with only one code path.
|
||||
- `variant(s)`: Defines an experiment with multiple code paths.
|
||||
- `behaviors`: Used to reference all possible code paths of an experiment, including the control.
|
||||
|
||||
### How it works
|
||||
|
||||
Use this decision tree diagram to understand how GLEX works. When an experiment runs,
|
||||
the following logic is executed to determine what variant should be provided,
|
||||
given how the experiment has been defined and using the provided context:
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
GP[General Pool/Population] --> Running?
|
||||
Running? -->|Yes| Cached?[Cached? / Pre-segmented?]
|
||||
Running? -->|No| Excluded[Control / No Tracking]
|
||||
Cached? -->|No| Excluded?
|
||||
Cached? -->|Yes| Cached[Cached Value]
|
||||
Excluded? -->|Yes| Excluded
|
||||
Excluded? -->|No| Segmented?
|
||||
Segmented? -->|Yes / Cached| VariantA
|
||||
Segmented? -->|No| Included?[Experiment Group?]
|
||||
Included? -->|Yes| Rollout
|
||||
Included? -->|No| Control
|
||||
Rollout -->|Cached| VariantA
|
||||
Rollout -->|Cached| VariantB
|
||||
Rollout -->|Cached| VariantC
|
||||
|
||||
classDef included fill:#380d75,color:#ffffff,stroke:none
|
||||
classDef excluded fill:#fca121,stroke:none
|
||||
classDef cached fill:#2e2e2e,color:#ffffff,stroke:none
|
||||
classDef default fill:#fff,stroke:#6e49cb
|
||||
|
||||
class VariantA,VariantB,VariantC included
|
||||
class Control,Excluded excluded
|
||||
class Cached cached
|
||||
```
|
||||
|
||||
## Implement an experiment
|
||||
## Implementing an experiment
|
||||
|
||||
[Examples](https://gitlab.com/gitlab-org/growth/growth/-/wikis/GLEX-Framework-code-examples)
|
||||
|
||||
|
@ -87,9 +53,9 @@ experiment in code. An experiment implementation can be as simple as:
|
|||
|
||||
```ruby
|
||||
experiment(:pill_color, actor: current_user) do |e|
|
||||
e.use { 'control' }
|
||||
e.try(:red) { 'red' }
|
||||
e.try(:blue) { 'blue' }
|
||||
e.control { 'control' }
|
||||
e.variant(:red) { 'red' }
|
||||
e.variant(:blue) { 'blue' }
|
||||
end
|
||||
```
|
||||
|
||||
|
@ -146,11 +112,11 @@ We can also implement this experiment in a HAML file with HTML wrappings:
|
|||
```haml
|
||||
#cta-interface
|
||||
- experiment(:pill_color, actor: current_user) do |e|
|
||||
- e.use do
|
||||
- e.control do
|
||||
.pill-button control
|
||||
- e.try(:red) do
|
||||
- e.variant(:red) do
|
||||
.pill-button.red red
|
||||
- e.try(:blue) do
|
||||
- e.variant(:blue) do
|
||||
.pill-button.blue blue
|
||||
```
|
||||
|
||||
|
@ -212,38 +178,30 @@ wouldn't be resolvable.
|
|||
|
||||
### Advanced experimentation
|
||||
|
||||
GLEX allows for two general implementation styles:
|
||||
There are two ways to implement an experiment:
|
||||
|
||||
1. The simple experiment style described previously.
|
||||
1. A more advanced style where an experiment class can be provided.
|
||||
1. A more advanced style where an experiment class is provided.
|
||||
|
||||
The advanced style is handled by naming convention, and works similar to what you
|
||||
would expect in Rails.
|
||||
|
||||
To generate a custom experiment class that can override the defaults in
|
||||
`ApplicationExperiment` (our base GLEX implementation), use the rails generator:
|
||||
`ApplicationExperiment` use the Rails generator:
|
||||
|
||||
```shell
|
||||
rails generate gitlab:experiment pill_color control red blue
|
||||
```
|
||||
|
||||
This generates an experiment class in `app/experiments/pill_color_experiment.rb`
|
||||
with the variants (or _behaviors_) we've provided to the generator. Here's an example
|
||||
of how that class would look after migrating the previous example into it:
|
||||
with the _behaviors_ we've provided to the generator. Here's an example
|
||||
of how that class would look after migrating our previous example into it:
|
||||
|
||||
```ruby
|
||||
class PillColorExperiment < ApplicationExperiment
|
||||
def control_behavior
|
||||
'control'
|
||||
end
|
||||
|
||||
def red_behavior
|
||||
'red'
|
||||
end
|
||||
|
||||
def blue_behavior
|
||||
'blue'
|
||||
end
|
||||
control { 'control' }
|
||||
variant(:red) { 'red' }
|
||||
variant(:blue) { 'blue' }
|
||||
end
|
||||
```
|
||||
|
||||
|
@ -254,13 +212,13 @@ providing the block we were initially providing, by explicitly calling `run`:
|
|||
experiment(:pill_color, actor: current_user).run
|
||||
```
|
||||
|
||||
The _behavior_ methods we defined in our experiment class represent the default
|
||||
implementation. You can still use the block syntax to override these _behavior_
|
||||
methods however, so the following would also be valid:
|
||||
The _behaviors_ we defined in our experiment class represent the default
|
||||
implementation. You can still use the block syntax to override these _behaviors_
|
||||
however, so the following would also be valid:
|
||||
|
||||
```ruby
|
||||
experiment(:pill_color, actor: current_user) do |e|
|
||||
e.use { '<strong>control</strong>' }
|
||||
e.control { '<strong>control</strong>' }
|
||||
end
|
||||
```
|
||||
|
||||
|
@ -279,11 +237,11 @@ variant, and any account older than 2 weeks old would be assigned the _blue_ var
|
|||
|
||||
```ruby
|
||||
class PillColorExperiment < ApplicationExperiment
|
||||
# ...registered behaviors
|
||||
|
||||
segment(variant: :red) { context.actor.first_name == 'Richard' }
|
||||
segment :old_account?, variant: :blue
|
||||
|
||||
# ...behaviors
|
||||
|
||||
private
|
||||
|
||||
def old_account?
|
||||
|
@ -315,9 +273,9 @@ be nothing - but no events are tracked in these cases as well.
|
|||
|
||||
```ruby
|
||||
class PillColorExperiment < ApplicationExperiment
|
||||
exclude :old_account?, ->{ context.actor.first_name == 'Richard' }
|
||||
# ...registered behaviors
|
||||
|
||||
# ...behaviors
|
||||
exclude :old_account?, ->{ context.actor.first_name == 'Richard' }
|
||||
|
||||
private
|
||||
|
||||
|
@ -327,23 +285,11 @@ class PillColorExperiment < ApplicationExperiment
|
|||
end
|
||||
```
|
||||
|
||||
We can also do exclusion when we run the experiment. For instance,
|
||||
if we wanted to prevent the inclusion of non-administrators in an experiment, consider
|
||||
the following experiment. This type of logic enables us to do complex experiments
|
||||
while preventing us from passing things into our experiments, because
|
||||
we want to minimize passing things into our experiments:
|
||||
|
||||
```ruby
|
||||
experiment(:pill_color, actor: current_user) do |e|
|
||||
e.exclude! unless can?(current_user, :admin_project, project)
|
||||
end
|
||||
```
|
||||
|
||||
You may also need to check exclusion in custom tracking logic by calling `should_track?`:
|
||||
|
||||
```ruby
|
||||
class PillColorExperiment < ApplicationExperiment
|
||||
# ...behaviors
|
||||
# ...registered behaviors
|
||||
|
||||
def expensive_tracking_logic
|
||||
return unless should_track?
|
||||
|
@ -353,16 +299,11 @@ class PillColorExperiment < ApplicationExperiment
|
|||
end
|
||||
```
|
||||
|
||||
Exclusion rules aren't the best way to determine if an experiment is active. Override
|
||||
the `enabled?` method for a high-level way of determining if an experiment should
|
||||
run and track. Make the `enabled?` check as efficient as possible because it's the
|
||||
first early opt-out path an experiment can implement.
|
||||
|
||||
### Tracking events
|
||||
|
||||
One of the most important aspects of experiments is gathering data and reporting on
|
||||
it. GLEX provides an interface that allows tracking events across an experiment.
|
||||
You can implement it consistently if you provide the same context between
|
||||
it. You can use the `track` method to track events across an experimental implementation.
|
||||
You can track events consistently to an experiment if you provide the same context between
|
||||
calls to your experiment. If you do not yet understand context, you should read
|
||||
about contexts now.
|
||||
|
||||
|
@ -373,13 +314,13 @@ the arguments you would normally use when
|
|||
of tracking an event in Ruby would be:
|
||||
|
||||
```ruby
|
||||
experiment(:pill_color, actor: current_user).track(:created)
|
||||
experiment(:pill_color, actor: current_user).track(:clicked)
|
||||
```
|
||||
|
||||
When you run an experiment with any of these examples, an `:assigned` event
|
||||
When you run an experiment with any of the examples so far, an `:assigned` event
|
||||
is tracked automatically by default. All events that are tracked from an
|
||||
experiment have a special
|
||||
[experiment context](https://gitlab.com/gitlab-org/iglu/-/blob/master/public/schemas/com.gitlab/gitlab_experiment/jsonschema/1-0-0)
|
||||
[experiment context](https://gitlab.com/gitlab-org/iglu/-/blob/master/public/schemas/com.gitlab/gitlab_experiment/jsonschema/1-0-3)
|
||||
added to the event. This can be used - typically by the data team - to create a connection
|
||||
between the events on a given experiment.
|
||||
|
||||
|
@ -390,28 +331,20 @@ event would be tracked at that time for them.
|
|||
|
||||
NOTE:
|
||||
GitLab tries to be sensitive and respectful of our customers regarding tracking,
|
||||
so GLEX allows us to implement an experiment without ever tracking identifying
|
||||
so our experimentation library allows us to implement an experiment without ever tracking identifying
|
||||
IDs. It's not always possible, though, based on experiment reporting requirements.
|
||||
You may be asked from time to time to track a specific record ID in experiments.
|
||||
The approach is largely up to the PM and engineer creating the implementation.
|
||||
No recommendations are provided here at this time.
|
||||
|
||||
## Test with RSpec
|
||||
## Testing with RSpec
|
||||
|
||||
This gem provides some RSpec helpers and custom matchers. These are in flux as of GitLab 13.10.
|
||||
|
||||
First, require the RSpec support file to mix in some of the basics:
|
||||
In the course of working with experiments, you'll probably want to utilize the RSpec
|
||||
tooling that's built in. This happens automatically for files in `spec/experiments`, but
|
||||
for other files and specs you want to include it in, you can specify the `:experiment` type:
|
||||
|
||||
```ruby
|
||||
require 'gitlab/experiment/rspec'
|
||||
```
|
||||
|
||||
You still need to include matchers and other aspects, which happens
|
||||
automatically for files in `spec/experiments`, but for other files and specs
|
||||
you want to include it in, you can specify the `:experiment` type:
|
||||
|
||||
```ruby
|
||||
it "tests", :experiment do
|
||||
it "tests experiments nicely", :experiment do
|
||||
end
|
||||
```
|
||||
|
||||
|
@ -421,28 +354,32 @@ You can stub experiments using `stub_experiments`. Pass it a hash using experime
|
|||
names as the keys, and the variants you want each to resolve to, as the values:
|
||||
|
||||
```ruby
|
||||
# Ensures the experiments named `:example` & `:example2` are both
|
||||
# "enabled" and that each will resolve to the given variant
|
||||
# (`:my_variant` & `:control` respectively).
|
||||
# Ensures the experiments named `:example` & `:example2` are both "enabled" and
|
||||
# that each will resolve to the given variant (`:my_variant` and `:control`
|
||||
# respectively).
|
||||
stub_experiments(example: :my_variant, example2: :control)
|
||||
|
||||
experiment(:example) do |e|
|
||||
e.enabled? # => true
|
||||
e.variant.name # => 'my_variant'
|
||||
e.assigned.name # => 'my_variant'
|
||||
end
|
||||
|
||||
experiment(:example2) do |e|
|
||||
e.enabled? # => true
|
||||
e.variant.name # => 'control'
|
||||
e.assigned.name # => 'control'
|
||||
end
|
||||
```
|
||||
|
||||
### Exclusion and segmentation matchers
|
||||
### Exclusion, segmentation, and behavior matchers
|
||||
|
||||
You can also test the exclusion and segmentation matchers.
|
||||
You can also test things like the registered behaviors, the exclusions, and
|
||||
segmentations using the matchers.
|
||||
|
||||
```ruby
|
||||
class ExampleExperiment < ApplicationExperiment
|
||||
control { }
|
||||
candidate { '_candidate_' }
|
||||
|
||||
exclude { context.actor.first_name == 'Richard' }
|
||||
segment(variant: :candidate) { context.actor.username == 'jejacks0n' }
|
||||
end
|
||||
|
@ -450,6 +387,10 @@ end
|
|||
excluded = double(username: 'rdiggitty', first_name: 'Richard')
|
||||
segmented = double(username: 'jejacks0n', first_name: 'Jeremy')
|
||||
|
||||
# register_behavior matcher
|
||||
expect(experiment(:example)).to register_behavior(:control)
|
||||
expect(experiment(:example)).to register_behavior(:candidate).with('_candidate_')
|
||||
|
||||
# exclude matcher
|
||||
expect(experiment(:example)).to exclude(actor: excluded)
|
||||
expect(experiment(:example)).not_to exclude(actor: segmented)
|
||||
|
@ -495,35 +436,36 @@ expect(experiment(:example)).to track(:my_event, value: 1, property: '_property_
|
|||
experiment(:example, :variant_name, foo: :bar).track(:my_event, value: 1, property: '_property_')
|
||||
```
|
||||
|
||||
### Recording and assignment tracking
|
||||
|
||||
To test assignment tracking and the `record!` method, you can use or adopt the following
|
||||
shared example: [tracks assignment and records the subject](https://gitlab.com/gitlab-org/gitlab/blob/master/spec/support/shared_examples/lib/gitlab/experimentation_shared_examples.rb).
|
||||
|
||||
## Experiments in the client layer
|
||||
|
||||
This is in flux as of GitLab 13.10, and can't be documented just yet.
|
||||
Any experiment that's been run in the request lifecycle surfaces in `window.gl.experiments`,
|
||||
and matches [this schema](https://gitlab.com/gitlab-org/iglu/-/blob/master/public/schemas/com.gitlab/gitlab_experiment/jsonschema/1-0-3)
|
||||
so it can be used when resolving experimentation in the client layer.
|
||||
|
||||
Any experiment that's been run in the request lifecycle surfaces in and `window.gl.experiments`,
|
||||
and matches [this schema](https://gitlab.com/gitlab-org/iglu/-/blob/master/public/schemas/com.gitlab/gitlab_experiment/jsonschema/1-0-0)
|
||||
so you can use it when resolving some concepts around experimentation in the client layer.
|
||||
Given that we've defined a class for our experiment, and have defined the variants for it, we can publish that experiment in a couple ways.
|
||||
|
||||
### Use experiments in Vue
|
||||
The first way is simply by running the experiment. Assuming the experiment has been run, it will surface in the client layer without having to do anything special.
|
||||
|
||||
With the `gitlab-experiment` component, you can define slots that match the name of the
|
||||
variants pushed to `window.gl.experiments`. For example, if we alter the `pill_color`
|
||||
experiment to just use the default variants of `control` and `candidate` like so:
|
||||
The second way doesn't run the experiment and is intended to be used if the experiment only needs to surface in the client layer. To accomplish this we can simply `.publish` the experiment. This won't run any logic, but does surface the experiment details in the client layer so they can be utilized there.
|
||||
|
||||
An example might be to publish an experiment in a `before_action` in a controller. Assuming we've defined the `PillColorExperiment` class, like we have above, we can surface it to the client by publishing it instead of running it:
|
||||
|
||||
```ruby
|
||||
def show
|
||||
experiment(:pill_color) do |e|
|
||||
e.use { } # control
|
||||
e.try { } # candidate
|
||||
end.run
|
||||
end
|
||||
before_action -> { experiment(:pill_color).publish }, only: [:show]
|
||||
```
|
||||
|
||||
We can make use of the named slots `control` and `candidate` in the Vue component:
|
||||
You can then see this surface in the JavaScript console:
|
||||
|
||||
```javascript
|
||||
window.gl.experiments // => { pill_color: { excluded: false, experiment: "pill_color", key: "ca63ac02", variant: "candidate" } }
|
||||
```
|
||||
|
||||
### Using experiments in Vue
|
||||
|
||||
With the `gitlab-experiment` component, you can define slots that match the name of the
|
||||
variants pushed to `window.gl.experiments`.
|
||||
|
||||
We can make use of the named slots in the Vue component, that match the behaviors defined in :
|
||||
|
||||
```vue
|
||||
<script>
|
||||
|
@ -534,23 +476,6 @@ export default {
|
|||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<gitlab-experiment name="pill_color">
|
||||
<template #control>
|
||||
<button class="bg-default">Click default button</button>
|
||||
</template>
|
||||
|
||||
<template #candidate>
|
||||
<button class="bg-red">Click red button</button>
|
||||
</template>
|
||||
</gitlab-experiment>
|
||||
</template>
|
||||
```
|
||||
|
||||
When you're coding for an experiment with multiple variants, you can use the variant names.
|
||||
For example, the Vue component for the previously-defined `pill_color` experiment with `red` and `blue` variants would look like this:
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<gitlab-experiment name="pill_color">
|
||||
<template #control>
|
||||
|
@ -628,7 +553,7 @@ is viewed as being either `on` or `off`, this isn't accurate for experiments.
|
|||
|
||||
Generally, `off` means that when we ask if a feature flag is enabled, it will always
|
||||
return `false`, and `on` means that it will always return `true`. An interim state,
|
||||
considered `conditional`, also exists. GLEX takes advantage of this trinary state of
|
||||
considered `conditional`, also exists. We take advantage of this trinary state of
|
||||
feature flags. To understand this `conditional` aspect: consider that either of these
|
||||
settings puts a feature flag into this state:
|
||||
|
||||
|
@ -638,7 +563,7 @@ settings puts a feature flag into this state:
|
|||
Conditional means that it returns `true` in some situations, but not all situations.
|
||||
|
||||
When a feature flag is disabled (meaning the state is `off`), the experiment is
|
||||
considered _inactive_. You can visualize this in the [decision tree diagram](#how-it-works)
|
||||
considered _inactive_. You can visualize this in the [decision tree diagram](https://gitlab.com/gitlab-org/ruby/gems/gitlab-experiment#how-it-works)
|
||||
as reaching the first `Running?` node, and traversing the negative path.
|
||||
|
||||
When a feature flag is rolled out to a `percentage_of_actors` or similar (meaning the
|
||||
|
|
|
@ -133,7 +133,7 @@ module API
|
|||
success Entities::ProjectImportStatus
|
||||
end
|
||||
post 'remote-import' do
|
||||
not_found! unless ::Feature.enabled?(:import_project_from_remote_file)
|
||||
not_found! unless ::Feature.enabled?(:import_project_from_remote_file, default_enabled: :yaml)
|
||||
|
||||
check_rate_limit! :project_import, scope: [current_user, :project_import]
|
||||
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
module QA
|
||||
RSpec.describe 'Package', :orchestrated, :registry, only: { pipeline: :main } do
|
||||
describe 'Dependency Proxy' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
let(:project) do
|
||||
Resource::Project.fabricate_via_api! do |project|
|
||||
project.name = 'dependency-proxy-project'
|
||||
|
@ -40,12 +42,13 @@ module QA
|
|||
runner.remove_via_api!
|
||||
end
|
||||
|
||||
where(:docker_client_version) do
|
||||
%w[docker:19.03.12 docker:20.10]
|
||||
where(:case_name, :docker_client_version, :testcase) do
|
||||
'using docker:19.03.12' | 'docker:19.03.12' | 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347605'
|
||||
'using docker:20.10' | 'docker:20.10' | 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347604'
|
||||
end
|
||||
|
||||
with_them do
|
||||
it "pulls an image using the dependency proxy" do
|
||||
it "pulls an image using the dependency proxy", testcase: params[:testcase] do
|
||||
Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do
|
||||
Resource::Repository::Commit.fabricate_via_api! do |commit|
|
||||
commit.project = project
|
||||
|
@ -58,7 +61,7 @@ module QA
|
|||
image: "#{docker_client_version}"
|
||||
services:
|
||||
- name: "#{docker_client_version}-dind"
|
||||
command: ["--insecure-registry=gitlab.test:80"]
|
||||
command: ["--insecure-registry=gitlab.test:80"]
|
||||
before_script:
|
||||
- apk add curl jq grep
|
||||
- echo $CI_DEPENDENCY_PROXY_SERVER
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
module QA
|
||||
RSpec.describe 'Package', :orchestrated, :packages, :object_storage do
|
||||
describe 'Helm Registry' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
include Runtime::Fixtures
|
||||
include_context 'packages registry qa scenario'
|
||||
|
||||
|
@ -10,101 +11,105 @@ module QA
|
|||
let(:package_version) { '1.3.7' }
|
||||
let(:package_type) { 'helm' }
|
||||
|
||||
%i[personal_access_token ci_job_token project_deploy_token].each do |authentication_token_type|
|
||||
context "using a #{authentication_token_type}" do
|
||||
let(:username) do
|
||||
case authentication_token_type
|
||||
when :personal_access_token
|
||||
Runtime::User.username
|
||||
when :ci_job_token
|
||||
'gitlab-ci-token'
|
||||
when :project_deploy_token
|
||||
project_deploy_token.username
|
||||
where(:case_name, :authentication_token_type, :testcase) do
|
||||
'using personal access token' | :personal_access_token | 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347586'
|
||||
'using ci job token' | :ci_job_token | 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347587'
|
||||
'using project deploy token' | :project_deploy_token | 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347588'
|
||||
end
|
||||
|
||||
with_them do
|
||||
let(:username) do
|
||||
case authentication_token_type
|
||||
when :personal_access_token
|
||||
Runtime::User.username
|
||||
when :ci_job_token
|
||||
'gitlab-ci-token'
|
||||
when :project_deploy_token
|
||||
project_deploy_token.username
|
||||
end
|
||||
end
|
||||
|
||||
let(:access_token) do
|
||||
case authentication_token_type
|
||||
when :personal_access_token
|
||||
personal_access_token
|
||||
when :ci_job_token
|
||||
'${CI_JOB_TOKEN}'
|
||||
when :project_deploy_token
|
||||
project_deploy_token.token
|
||||
end
|
||||
end
|
||||
|
||||
it "pushes and pulls a helm chart", testcase: params[:testcase] do
|
||||
Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do
|
||||
Resource::Repository::Commit.fabricate_via_api! do |commit|
|
||||
helm_upload_yaml = ERB.new(read_fixture('package_managers/helm', 'helm_upload_package.yaml.erb')).result(binding)
|
||||
helm_chart_yaml = ERB.new(read_fixture('package_managers/helm', 'Chart.yaml.erb')).result(binding)
|
||||
|
||||
commit.project = package_project
|
||||
commit.commit_message = 'Add .gitlab-ci.yml'
|
||||
commit.add_files([
|
||||
{
|
||||
file_path: '.gitlab-ci.yml',
|
||||
content: helm_upload_yaml
|
||||
},
|
||||
{
|
||||
file_path: 'Chart.yaml',
|
||||
content: helm_chart_yaml
|
||||
}
|
||||
])
|
||||
end
|
||||
end
|
||||
|
||||
let(:access_token) do
|
||||
case authentication_token_type
|
||||
when :personal_access_token
|
||||
personal_access_token
|
||||
when :ci_job_token
|
||||
'${CI_JOB_TOKEN}'
|
||||
when :project_deploy_token
|
||||
project_deploy_token.token
|
||||
package_project.visit!
|
||||
|
||||
Flow::Pipeline.visit_latest_pipeline
|
||||
|
||||
Page::Project::Pipeline::Show.perform do |pipeline|
|
||||
pipeline.click_job('deploy')
|
||||
end
|
||||
|
||||
Page::Project::Job::Show.perform do |job|
|
||||
expect(job).to be_successful(timeout: 800)
|
||||
end
|
||||
|
||||
Page::Project::Menu.perform(&:click_packages_link)
|
||||
|
||||
Page::Project::Packages::Index.perform do |index|
|
||||
expect(index).to have_package(package_name)
|
||||
|
||||
index.click_package(package_name)
|
||||
end
|
||||
|
||||
Page::Project::Packages::Show.perform do |show|
|
||||
expect(show).to have_package_info(package_name, package_version)
|
||||
end
|
||||
|
||||
Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do
|
||||
Resource::Repository::Commit.fabricate_via_api! do |commit|
|
||||
helm_install_yaml = ERB.new(read_fixture('package_managers/helm', 'helm_install_package.yaml.erb')).result(binding)
|
||||
|
||||
commit.project = client_project
|
||||
commit.commit_message = 'Add .gitlab-ci.yml'
|
||||
commit.add_files([
|
||||
{
|
||||
file_path: '.gitlab-ci.yml',
|
||||
content: helm_install_yaml
|
||||
}
|
||||
])
|
||||
end
|
||||
end
|
||||
|
||||
it "pushes and pulls a helm chart" do
|
||||
Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do
|
||||
Resource::Repository::Commit.fabricate_via_api! do |commit|
|
||||
helm_upload_yaml = ERB.new(read_fixture('package_managers/helm', 'helm_upload_package.yaml.erb')).result(binding)
|
||||
helm_chart_yaml = ERB.new(read_fixture('package_managers/helm', 'Chart.yaml.erb')).result(binding)
|
||||
client_project.visit!
|
||||
|
||||
commit.project = package_project
|
||||
commit.commit_message = 'Add .gitlab-ci.yml'
|
||||
commit.add_files([
|
||||
{
|
||||
file_path: '.gitlab-ci.yml',
|
||||
content: helm_upload_yaml
|
||||
},
|
||||
{
|
||||
file_path: 'Chart.yaml',
|
||||
content: helm_chart_yaml
|
||||
}
|
||||
])
|
||||
end
|
||||
end
|
||||
Flow::Pipeline.visit_latest_pipeline
|
||||
|
||||
package_project.visit!
|
||||
Page::Project::Pipeline::Show.perform do |pipeline|
|
||||
pipeline.click_job('pull')
|
||||
end
|
||||
|
||||
Flow::Pipeline.visit_latest_pipeline
|
||||
|
||||
Page::Project::Pipeline::Show.perform do |pipeline|
|
||||
pipeline.click_job('deploy')
|
||||
end
|
||||
|
||||
Page::Project::Job::Show.perform do |job|
|
||||
expect(job).to be_successful(timeout: 800)
|
||||
end
|
||||
|
||||
Page::Project::Menu.perform(&:click_packages_link)
|
||||
|
||||
Page::Project::Packages::Index.perform do |index|
|
||||
expect(index).to have_package(package_name)
|
||||
|
||||
index.click_package(package_name)
|
||||
end
|
||||
|
||||
Page::Project::Packages::Show.perform do |show|
|
||||
expect(show).to have_package_info(package_name, package_version)
|
||||
end
|
||||
|
||||
Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do
|
||||
Resource::Repository::Commit.fabricate_via_api! do |commit|
|
||||
helm_install_yaml = ERB.new(read_fixture('package_managers/helm', 'helm_install_package.yaml.erb')).result(binding)
|
||||
|
||||
commit.project = client_project
|
||||
commit.commit_message = 'Add .gitlab-ci.yml'
|
||||
commit.add_files([
|
||||
{
|
||||
file_path: '.gitlab-ci.yml',
|
||||
content: helm_install_yaml
|
||||
}
|
||||
])
|
||||
end
|
||||
end
|
||||
|
||||
client_project.visit!
|
||||
|
||||
Flow::Pipeline.visit_latest_pipeline
|
||||
|
||||
Page::Project::Pipeline::Show.perform do |pipeline|
|
||||
pipeline.click_job('pull')
|
||||
end
|
||||
|
||||
Page::Project::Job::Show.perform do |job|
|
||||
expect(job).to be_successful(timeout: 800)
|
||||
end
|
||||
Page::Project::Job::Show.perform do |job|
|
||||
expect(job).to be_successful(timeout: 800)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -68,10 +68,10 @@ module QA
|
|||
another_project.remove_via_api!
|
||||
end
|
||||
|
||||
where(:authentication_token_type, :token_name) do
|
||||
:personal_access_token | 'Personal Access Token'
|
||||
:ci_job_token | 'CI Job Token'
|
||||
:project_deploy_token | 'Deploy Token'
|
||||
where(:case_name, :authentication_token_type, :token_name, :testcase) do
|
||||
'using personal access token' | :personal_access_token | 'Personal Access Token' | 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347600'
|
||||
'using ci job token' | :ci_job_token | 'CI Job Token' | 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347599'
|
||||
'using project deploy token' | :project_deploy_token | 'Deploy Token' | 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347598'
|
||||
end
|
||||
|
||||
with_them do
|
||||
|
@ -86,7 +86,7 @@ module QA
|
|||
end
|
||||
end
|
||||
|
||||
it "push and pull a npm package via CI using a #{params[:token_name]}" do
|
||||
it 'push and pull a npm package via CI', testcase: params[:testcase] do
|
||||
Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do
|
||||
npm_upload_yaml = ERB.new(read_fixture('package_managers/npm', 'npm_upload_package_instance.yaml.erb')).result(binding)
|
||||
package_json = ERB.new(read_fixture('package_managers/npm', 'package_instance.json.erb')).result(binding)
|
||||
|
|
|
@ -59,10 +59,10 @@ module QA
|
|||
project.remove_via_api!
|
||||
end
|
||||
|
||||
where(:authentication_token_type, :token_name) do
|
||||
:personal_access_token | 'Personal Access Token'
|
||||
:ci_job_token | 'CI Job Token'
|
||||
:project_deploy_token | 'Deploy Token'
|
||||
where(:case_name, :authentication_token_type, :token_name, :testcase) do
|
||||
'using personal access token' | :personal_access_token | 'Personal Access Token' | 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347592'
|
||||
'using ci job token' | :ci_job_token | 'CI Job Token' | 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347594'
|
||||
'using project deploy token' | :project_deploy_token | 'Deploy Token' | 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347593'
|
||||
end
|
||||
|
||||
with_them do
|
||||
|
@ -77,7 +77,7 @@ module QA
|
|||
end
|
||||
end
|
||||
|
||||
it "push and pull a npm package via CI using a #{params[:token_name]}" do
|
||||
it 'push and pull a npm package via CI', testcase: params[:testcase] do
|
||||
Resource::Repository::Commit.fabricate_via_api! do |commit|
|
||||
npm_upload_install_yaml = ERB.new(read_fixture('package_managers/npm', 'npm_upload_install_package_project.yaml.erb')).result(binding)
|
||||
package_json = ERB.new(read_fixture('package_managers/npm', 'package_project.json.erb')).result(binding)
|
||||
|
|
|
@ -63,10 +63,10 @@ module QA
|
|||
package.remove_via_api!
|
||||
end
|
||||
|
||||
where(:authentication_token_type, :token_name) do
|
||||
:personal_access_token | 'Personal Access Token'
|
||||
:ci_job_token | 'CI Job Token'
|
||||
:group_deploy_token | 'Deploy Token'
|
||||
where(:case_name, :authentication_token_type, :token_name, :testcase) do
|
||||
'using personal access token' | :personal_access_token | 'Personal Access Token' | 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347597'
|
||||
'using ci job token' | :ci_job_token | 'CI Job Token' | 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347595'
|
||||
'using group deploy token' | :group_deploy_token | 'Deploy Token' | 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347596'
|
||||
end
|
||||
|
||||
with_them do
|
||||
|
@ -92,7 +92,7 @@ module QA
|
|||
end
|
||||
end
|
||||
|
||||
it "publishes a nuget package at the project level, installs and deletes it at the group level using a #{params[:token_name]}" do
|
||||
it 'publishes a nuget package at the project level, installs and deletes it at the group level', testcase: params[:testcase] do
|
||||
Flow::Login.sign_in
|
||||
|
||||
Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do
|
||||
|
|
|
@ -117,7 +117,7 @@ RSpec.describe ApplicationExperiment, :experiment do
|
|||
describe '#publish_to_database' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
let(:publish_to_database) { application_experiment.publish_to_database }
|
||||
let(:publish_to_database) { ActiveSupport::Deprecation.silence { application_experiment.publish_to_database } }
|
||||
|
||||
shared_examples 'does not record to the database' do
|
||||
it 'does not create an experiment record' do
|
||||
|
@ -358,11 +358,11 @@ RSpec.describe ApplicationExperiment, :experiment do
|
|||
stub_feature_flags(namespaced_stub: true)
|
||||
end
|
||||
|
||||
it "returns the first variant name" do
|
||||
it "returns an assigned name" do
|
||||
application_experiment.try(:variant1) {}
|
||||
application_experiment.try(:variant2) {}
|
||||
|
||||
expect(application_experiment.variant.name).to eq('variant1')
|
||||
expect(application_experiment.variant.name).to eq('variant2')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,4 +8,33 @@ describe('table_utility', () => {
|
|||
expect(tableUtils.thWidthClass(width)).toBe(`gl-w-${width}p ${DEFAULT_TH_CLASSES}`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sortObjectToString', () => {
|
||||
it('returns the expected sorting string ending in "DESC" when sortDesc is true', () => {
|
||||
expect(tableUtils.sortObjectToString({ sortBy: 'mergedAt', sortDesc: true })).toBe(
|
||||
'MERGED_AT_DESC',
|
||||
);
|
||||
});
|
||||
|
||||
it('returns the expected sorting string ending in "ASC" when sortDesc is false', () => {
|
||||
expect(tableUtils.sortObjectToString({ sortBy: 'mergedAt', sortDesc: false })).toBe(
|
||||
'MERGED_AT_ASC',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sortStringToObject', () => {
|
||||
it.each`
|
||||
sortBy | sortDesc | sortString
|
||||
${'mergedAt'} | ${true} | ${'MERGED_AT_DESC'}
|
||||
${'mergedAt'} | ${false} | ${'MERGED_AT_ASC'}
|
||||
${'severity'} | ${true} | ${'SEVERITY_DESC'}
|
||||
${'severity'} | ${false} | ${'SEVERITY_ASC'}
|
||||
`(
|
||||
'returns $sortString when sortBy = "$sortBy" and sortDesc = "sortDesc"',
|
||||
({ sortBy, sortDesc, sortString }) => {
|
||||
expect(tableUtils.sortStringToObject(sortString)).toStrictEqual({ sortBy, sortDesc });
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -24,6 +24,20 @@ describe('Settings Panels', () => {
|
|||
|
||||
expect(isExpanded(panel)).toBe(true);
|
||||
});
|
||||
|
||||
it('should expand panel containing linked hash', () => {
|
||||
window.location.hash = '#group_description';
|
||||
|
||||
const panel = document.querySelector('#js-general-settings');
|
||||
// Our test environment automatically expands everything so we need to clear that out first
|
||||
panel.classList.remove('expanded');
|
||||
|
||||
expect(isExpanded(panel)).toBe(false);
|
||||
|
||||
initSettingsPanels();
|
||||
|
||||
expect(isExpanded(panel)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('does not change the text content of triggers', () => {
|
||||
|
|
|
@ -5,22 +5,29 @@ require 'spec_helper'
|
|||
RSpec.describe Projects::ClusterAgentsHelper do
|
||||
describe '#js_cluster_agent_details_data' do
|
||||
let_it_be(:project) { create(:project) }
|
||||
let_it_be(:current_user) { create(:user) }
|
||||
|
||||
let(:user_can_admin_vulerability) { true }
|
||||
let(:agent_name) { 'agent-name' }
|
||||
|
||||
before do
|
||||
allow(helper).to receive(:current_user).and_return(current_user)
|
||||
allow(helper)
|
||||
.to receive(:can?)
|
||||
.with(current_user, :admin_vulnerability, project)
|
||||
.and_return(user_can_admin_vulerability)
|
||||
end
|
||||
|
||||
subject { helper.js_cluster_agent_details_data(agent_name, project) }
|
||||
|
||||
it 'returns name' do
|
||||
expect(subject[:agent_name]).to eq(agent_name)
|
||||
end
|
||||
|
||||
it 'returns project path' do
|
||||
expect(subject[:project_path]).to eq(project.full_path)
|
||||
end
|
||||
|
||||
it 'returns string contants' do
|
||||
expect(subject[:activity_empty_state_image]).to be_kind_of(String)
|
||||
expect(subject[:empty_state_svg_path]).to be_kind_of(String)
|
||||
end
|
||||
it {
|
||||
is_expected.to match({
|
||||
agent_name: agent_name,
|
||||
project_path: project.full_path,
|
||||
activity_empty_state_image: kind_of(String),
|
||||
empty_state_svg_path: kind_of(String),
|
||||
can_admin_vulnerability: "true"
|
||||
})
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -10,9 +10,23 @@ RSpec.configure do |config|
|
|||
# Disable all caching for experiments in tests.
|
||||
config.before do
|
||||
allow(Gitlab::Experiment::Configuration).to receive(:cache).and_return(nil)
|
||||
|
||||
# Disable all deprecation warnings in the test environment, which can be
|
||||
# resolved one by one and tracked in:
|
||||
#
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/issues/350944
|
||||
allow(Gitlab::Experiment::Configuration).to receive(:deprecator).and_wrap_original do |method, version|
|
||||
method.call(version).tap do |deprecator|
|
||||
deprecator.silenced = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
config.before(:each, :experiment) do
|
||||
stub_snowplow
|
||||
end
|
||||
end
|
||||
|
||||
# Once you've resolved a given deprecation, you can disallow it here, which
|
||||
# will raise an exception if it's used anywhere.
|
||||
ActiveSupport::Deprecation.disallowed_warnings << "`experiment_group?` is deprecated"
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.shared_examples 'tracks assignment and records the subject' do |experiment, subject_type|
|
||||
before do
|
||||
stub_experiments(experiment => true)
|
||||
end
|
||||
|
||||
it 'tracks the assignment', :experiment do
|
||||
expect(experiment(experiment))
|
||||
.to track(:assignment)
|
||||
|
@ -11,9 +15,7 @@ RSpec.shared_examples 'tracks assignment and records the subject' do |experiment
|
|||
end
|
||||
|
||||
it 'records the subject' do
|
||||
stub_experiments(experiment => :candidate)
|
||||
|
||||
expect(Experiment).to receive(:add_subject).with(experiment.to_s, variant: :experimental, subject: subject)
|
||||
expect(Experiment).to receive(:add_subject).with(experiment.to_s, variant: anything, subject: subject)
|
||||
|
||||
action
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue