Commit ec50ff55 authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab master

parents acad5446 977a54a9
......@@ -376,6 +376,17 @@ db:rollback:
- bundle exec rake db:migrate VERSION=20181228175414
- bundle exec rake db:migrate SKIP_SCHEMA_VERSION_CHECK=true
db:gitlabcom-database-testing:
extends: .rails:rules:ee-and-foss-mr-with-migration
stage: test
image: ruby:2.7-alpine
needs: []
allow_failure: true
script:
- source scripts/utils.sh
- install_gitlab_gem
- ./scripts/trigger-build gitlab-com-database-testing
gitlab:setup:
extends: .db-job-base
variables:
......
......@@ -477,7 +477,7 @@ gem 'flipper', '~> 0.17.1'
gem 'flipper-active_record', '~> 0.17.1'
gem 'flipper-active_support_cache_store', '~> 0.17.1'
gem 'unleash', '~> 0.1.5'
gem 'gitlab-experiment', '~> 0.4.8'
gem 'gitlab-experiment', '~> 0.4.5'
# Structured logging
gem 'lograge', '~> 0.5'
......
......@@ -424,7 +424,7 @@ GEM
github-markup (1.7.0)
gitlab-chronic (0.10.5)
numerizer (~> 0.2)
gitlab-experiment (0.4.8)
gitlab-experiment (0.4.5)
activesupport (>= 3.0)
scientist (~> 1.5, >= 1.5.0)
gitlab-fog-azure-rm (1.0.0)
......@@ -1364,7 +1364,7 @@ DEPENDENCIES
gitaly (~> 13.8.0.pre.rc3)
github-markup (~> 1.7.0)
gitlab-chronic (~> 0.10.5)
gitlab-experiment (~> 0.4.8)
gitlab-experiment (~> 0.4.5)
gitlab-fog-azure-rm (~> 1.0)
gitlab-labkit (= 0.14.0)
gitlab-license (~> 1.0)
......
<script>
import { GlIcon, GlLink } from '@gitlab/ui';
import { GlButton, GlButtonGroup, GlIcon, GlLink } from '@gitlab/ui';
import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import timeagoMixin from '~/vue_shared/mixins/timeago';
export default {
components: {
TimeagoTooltip,
GlButton,
GlButtonGroup,
GlIcon,
GlLink,
TimeagoTooltip,
},
mixins: [timeagoMixin],
props: {
......@@ -36,7 +38,7 @@ export default {
</script>
<template>
<div class="block">
<div class="title font-weight-bold">{{ s__('Job|Job artifacts') }}</div>
<div class="title gl-font-weight-bold">{{ s__('Job|Job artifacts') }}</div>
<p
v-if="isExpired || willExpire"
class="build-detail-row"
......@@ -61,32 +63,29 @@ export default {
)
}}</span>
</p>
<div class="btn-group d-flex gl-mt-3" role="group">
<gl-link
<gl-button-group class="gl-display-flex gl-mt-3">
<gl-button
v-if="artifact.keep_path"
:href="artifact.keep_path"
class="btn btn-sm btn-default"
data-method="post"
data-testid="keep-artifacts"
>{{ s__('Job|Keep') }}</gl-link
>{{ s__('Job|Keep') }}</gl-button
>
<gl-link
<gl-button
v-if="artifact.download_path"
:href="artifact.download_path"
class="btn btn-sm btn-default"
download
rel="nofollow"
data-testid="download-artifacts"
>{{ s__('Job|Download') }}</gl-link
download
>{{ s__('Job|Download') }}</gl-button
>
<gl-link
<gl-button
v-if="artifact.browse_path"
:href="artifact.browse_path"
class="btn btn-sm btn-default"
data-testid="browse-artifacts"
data-qa-selector="browse_artifacts_button"
>{{ s__('Job|Browse') }}</gl-link
>{{ s__('Job|Browse') }}</gl-button
>
</div>
</gl-button-group>
</div>
</template>
......@@ -15,7 +15,6 @@
@import 'framework/badges';
@import 'framework/calendar';
@import 'framework/callout';
@import 'framework/carousel';
@import 'framework/common';
@import 'framework/dropdowns';
@import 'framework/files';
......
// Notes on the classes:
//
// 1. .carousel.pointer-event should ideally be pan-y (to allow for users to scroll vertically)
// even when their scroll action started on a carousel, but for compatibility (with Firefox)
// we're preventing all actions instead
// 2. The .carousel-item-left and .carousel-item-right is used to indicate where
// the active slide is heading.
// 3. .active.carousel-item is the current slide.
// 4. .active.carousel-item-left and .active.carousel-item-right is the current
// slide in its in-transition state. Only one of these occurs at a time.
// 5. .carousel-item-next.carousel-item-left and .carousel-item-prev.carousel-item-right
// is the upcoming slide in transition.
.carousel {
position: relative;
&.pointer-event {
touch-action: pan-y;
}
}
.carousel-inner {
position: relative;
width: 100%;
overflow: hidden;
@include clearfix();
}
.carousel-item {
position: relative;
display: none;
float: left;
width: 100%;
margin-right: -100%;
backface-visibility: hidden;
@include transition($carousel-transition);
}
.carousel-item.active,
.carousel-item-next,
.carousel-item-prev {
display: block;
}
.carousel-item-next:not(.carousel-item-left),
.active.carousel-item-right {
transform: translateX(100%);
}
.carousel-item-prev:not(.carousel-item-right),
.active.carousel-item-left {
transform: translateX(-100%);
}
//
// Alternate transitions
//
.carousel-fade {
.carousel-item {
opacity: 0;
transition-property: opacity;
transform: none;
}
.carousel-item.active,
.carousel-item-next.carousel-item-left,
.carousel-item-prev.carousel-item-right {
z-index: 1;
opacity: 1;
}
.active.carousel-item-left,
.active.carousel-item-right {
z-index: 0;
opacity: 0;
@include transition(0s $carousel-transition-duration opacity);
}
}
//
// Left/right controls for nav
//
.carousel-control-prev,
.carousel-control-next {
position: absolute;
top: 0;
bottom: 0;
z-index: 1;
// Use flex for alignment (1-3)
display: flex; // 1. allow flex styles
align-items: center; // 2. vertically center contents
justify-content: center; // 3. horizontally center contents
width: $carousel-control-width;
color: $carousel-control-color;
text-align: center;
opacity: $carousel-control-opacity;
@include transition($carousel-control-transition);
// Hover/focus state
@include hover-focus {
color: $carousel-control-color;
text-decoration: none;
outline: 0;
opacity: $carousel-control-hover-opacity;
}
}
.carousel-control-prev {
left: 0;
@if $enable-gradients {
background: linear-gradient(90deg, rgba($black, 0.25), rgba($black, 0.001));
}
}
.carousel-control-next {
right: 0;
@if $enable-gradients {
background: linear-gradient(270deg, rgba($black, 0.25), rgba($black, 0.001));
}
}
// Icons for within
.carousel-control-prev-icon,
.carousel-control-next-icon {
display: inline-block;
width: $carousel-control-icon-width;
height: $carousel-control-icon-width;
background: no-repeat 50% / 100% 100%;
}
.carousel-control-prev-icon {
background-image: $carousel-control-prev-icon-bg;
}
.carousel-control-next-icon {
background-image: $carousel-control-next-icon-bg;
}
// Optional indicator pips
//
// Add an ordered list with the following class and add a list item for each
// slide your carousel holds.
.carousel-indicators {
position: absolute;
right: 0;
bottom: 0;
left: 0;
z-index: 15;
display: flex;
justify-content: center;
padding-left: 0; // override <ol> default
// Use the .carousel-control's width as margin so we don't overlay those
margin-right: $carousel-control-width;
margin-left: $carousel-control-width;
list-style: none;
li {
box-sizing: content-box;
flex: 0 1 auto;
width: $carousel-indicator-width;
height: $carousel-indicator-height;
margin-right: $carousel-indicator-spacer;
margin-left: $carousel-indicator-spacer;
text-indent: -999px;
cursor: pointer;
background-color: $carousel-indicator-active-bg;
background-clip: padding-box;
// Use transparent borders to increase the hit area by 10px on top and bottom.
border-top: $carousel-indicator-hit-area-height solid transparent;
border-bottom: $carousel-indicator-hit-area-height solid transparent;
opacity: 0.5;
@include transition($carousel-indicator-transition);
}
.active {
opacity: 1;
}
}
// Optional captions
//
//
.carousel-caption {
position: absolute;
right: (100% - $carousel-caption-width) / 2;
bottom: 20px;
left: (100% - $carousel-caption-width) / 2;
z-index: 10;
padding-top: 20px;
padding-bottom: 20px;
color: $carousel-caption-color;
text-align: center;
}
......@@ -871,6 +871,27 @@ $add-to-slack-gif-max-width: 850px;
$add-to-slack-well-max-width: 750px;
$add-to-slack-logo-size: 100px;
/*
Security & Compliance Carousel
*/
$security-and-compliance-carousel-image-carousel-width: 1000px;
$security-and-compliance-carousel-image-discover-button-width: 45%;
$security-and-compliance-carousel-image-discover-buttons-max-width: 280px;
$security-and-compliance-carousel-image-discover-footer-max-width: 500px;
$security-and-compliance-carousel-image-discover-feedback-width: 30%;
$security-and-compliance-carousel-image-discover-text-carousel-max-width: 650px;
$security-and-compliance-carousel-image-discover-text-carousel-caption-height: 100%;
$security-and-compliance-carousel-image-discover-text-carousel-caption-max-width: 500px;
$security-and-compliance-carousel-control-icon-width: 10px;
$security-and-compliance-carousel-control-position: -5%;
$security-and-compliance-carousel-inner-width: 90%;
$security-and-compliance-carousel-indicators-bottom: -20px;
$security-and-compliance-carousel-indicators-bottom-lg: -15px;
$security-and-compliance-carousel-indicators-dimension: 6px;
$security-and-compliance-carousel-indicators-border-radius: 100%;
$security-and-compliance-carousel-prev-icon-background: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23666666' viewBox='0 0 8 8'%3E%3Cpath d='M5.25 0l-4 4 4 4 1.5-1.5-2.5-2.5 2.5-2.5-1.5-1.5z'/%3E%3C/svg%3E");
$security-and-compliance-carousel-next-icon-background: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23666666' viewBox='0 0 8 8'%3E%3Cpath d='M2.75 0l-1.5 1.5 2.5 2.5-2.5 2.5 1.5 1.5 4-4-4-4z'/%3E%3C/svg%3E");
/*
Popup
*/
......
......@@ -59,7 +59,8 @@ class Projects::IssuesController < Projects::ApplicationController
around_action :allow_gitaly_ref_name_caching, only: [:discussions]
before_action :run_null_hypothesis_experiment,
only: [:index, :new, :create]
only: [:index, :new, :create],
if: -> { Feature.enabled?(:gitlab_experiments) }
respond_to :html
......
# frozen_string_literal: true
class ApplicationExperiment < Gitlab::Experiment
def enabled?
return false if Feature::Definition.get(name).nil? # there has to be a feature flag yaml file
return false unless Gitlab.dev_env_or_com? # we're in an environment that allows experiments
Feature.get(name).state != :off # rubocop:disable Gitlab/AvoidFeatureGet
end
def publish(_result)
track(:assignment) # track that we've assigned a variant for this context
Gon.global.push({ experiment: { name => signature } }, true) # push to client
end
def track(action, **event_args)
return unless should_track? # no events for opted out actors or excluded subjects
return if excluded? # no events for opted out actors or excluded subjects
Gitlab::Tracking.event(name, action.to_s, **event_args.merge(
context: (event_args[:context] || []) << SnowplowTracker::SelfDescribingJson.new(
......
......@@ -184,6 +184,7 @@ module Gitlab
config.assets.precompile << "page_bundles/build.css"
config.assets.precompile << "page_bundles/ci_status.css"
config.assets.precompile << "page_bundles/cycle_analytics.css"
config.assets.precompile << "page_bundles/security_discover.css"
config.assets.precompile << "page_bundles/dev_ops_report.css"
config.assets.precompile << "page_bundles/environments.css"
config.assets.precompile << "page_bundles/epics.css"
......
---
name: gitlab_experiments
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/45840
rollout_issue_url:
milestone: '13.7'
type: development
group: group::adoption
default_enabled: false
......@@ -107,13 +107,13 @@ export default {
<template>
<div class="discover-box">
<h2 class="discover-title center gl-text-gray-900 gl-mx-auto">
<h2 class="discover-title gl-text-center gl-text-gray-900 gl-mx-auto">
{{ $options.i18n.discoverTitle }}
</h2>
<div class="discover-carousels">
<gl-carousel
v-model="slide"
class="discover-carousel discover-image-carousel gl-mx-auto gl-text-center gl-border-solid gl-border-1 gl-bg-gray-10"
class="discover-carousel discover-image-carousel gl-mx-auto gl-text-center gl-border-solid gl-border-1 gl-bg-gray-10 gl-border-gray-50"
no-wrap
controls
:interval="0"
......@@ -139,8 +139,8 @@ export default {
</template>
</gl-carousel-slide>
</gl-carousel>
<div class="discover-footer mx-auto my-0">
<p class="gl-text-gray-900 text-center mb-5">
<div class="discover-footer gl-mx-auto gl-my-0">
<p class="gl-text-gray-900 gl-text-center mb-7">
<gl-sprintf :message="$options.i18n.discoverPlanCaption">
<template #link="{ content }">
<gl-link
......@@ -154,7 +154,9 @@ export default {
</p>
</div>
</div>
<div class="discover-buttons d-flex flex-nowrap flex-row justify-content-between mx-auto my-0">
<div
class="discover-buttons gl-display-flex gl-flex-direction-row gl-justify-content-space-between gl-mx-auto"
>
<gl-button
class="discover-button-upgrade"
v-bind="discoverButtonProps"
......@@ -176,7 +178,7 @@ export default {
{{ $options.i18n.discoverTrialLabel }}
</gl-button>
</div>
<div id="tooltipcontainer" class="discover-feedback w-30p position-fixed">
<div id="tooltipcontainer" class="discover-feedback gl-fixed">
<gl-button
v-gl-tooltip:tooltipcontainer.left
:title="$options.i18n.discoverFeedbackLabel"
......
@import 'page_bundles/mixins_and_variables_and_functions';
.discover-box {
.discover-image-carousel {
padding-top: $gl-padding-24;
padding-bottom: $gl-padding-24;
max-width: $security-and-compliance-carousel-image-carousel-width;
@media (min-width: map-get($grid-breakpoints, lg)) {
padding-top: $gl-padding-32;
padding-bottom: $gl-padding-32;
}
}
.discover-title {
margin-top: $gl-spacing-scale-8;
margin-bottom: $gl-spacing-scale-8;
}
.discover-carousels {
padding-left: $gl-padding-32;
padding-right: $gl-padding-32;
margin-left: auto;
margin-right: auto;
}
.discover-carousel-img {
margin-bottom: $gl-padding-12;
border-radius: $border-radius-default;
}
.discover-button {
width: $security-and-compliance-carousel-image-discover-button-width;
}
.discover-buttons {
max-width: $security-and-compliance-carousel-image-discover-buttons-max-width;
}
.discover-footer {
margin: $gl-spacing-scale-8 auto $gl-spacing-scale-1;
max-width: $security-and-compliance-carousel-image-discover-footer-max-width;
}
.discover-feedback {
width: $security-and-compliance-carousel-image-discover-feedback-width;
bottom: $gl-spacing-scale-5;
right: $gl-spacing-scale-5;
text-align: right;
}
.discover-text-carousel {
max-width: $security-and-compliance-carousel-image-discover-text-carousel-max-width;
.carousel-caption {
height: $security-and-compliance-carousel-image-discover-text-carousel-caption-height;
max-width: $security-and-compliance-carousel-image-discover-text-carousel-caption-max-width;
margin-left: auto;
margin-right: auto;
}
}
.carousel-control-next-icon,
.carousel-control-prev-icon {
width: $security-and-compliance-carousel-control-icon-width;
@media (min-width: map-get($grid-breakpoints, md)) {
width: inherit;
}
}
.carousel-control-next {
right: $security-and-compliance-carousel-control-position;
}
.carousel-control-prev {
left: $security-and-compliance-carousel-control-position;
}
.discover-carousel {
margin-bottom: $gl-spacing-scale-7;
.carousel-inner {
width: $security-and-compliance-carousel-inner-width;
margin-left: auto;
margin-right: auto;
}
.carousel-indicators {
bottom: $security-and-compliance-carousel-indicators-bottom;
@media (min-width: map-get($grid-breakpoints, lg)) {
bottom: $security-and-compliance-carousel-indicators-bottom-lg;
}
li {
background-color: $gray-500;
width: $security-and-compliance-carousel-indicators-dimension;
height: $security-and-compliance-carousel-indicators-dimension;
border-radius: $security-and-compliance-carousel-indicators-border-radius;
margin-right: $gl-spacing-scale-5;
@media (min-width: map-get($grid-breakpoints, md)) {
width: $gl-spacing-scale-3;
height: $gl-spacing-scale-3;
}
}
.active {
background-color: $gray-500;
}
}
.carousel-control-prev-icon {
background-image: $security-and-compliance-carousel-prev-icon-background;
}
.carousel-control-next-icon {
background-image: $security-and-compliance-carousel-next-icon-background;
}
}
}
......@@ -176,137 +176,3 @@
}
}
}
.discover-box {
.discover-image-carousel {
border-color: $gray-50;
padding-top: 20px;
padding-bottom: 20px;
max-width: 1000px;
@media (min-width: map-get($grid-breakpoints, lg)) {
padding-top: 30px;
padding-bottom: 30px;
}
}
.discover-title {
margin-top: 40px;
margin-bottom: 40px;
}
.discover-carousels {
padding-left: 30px;
padding-right: 30px;
margin-left: auto;
margin-right: auto;
}
.discover-carousel-img {
margin-bottom: 12px;
border-radius: $border-radius-default;
}
.discover-button {
width: 45% !important;
}
.discover-buttons {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: space-between;
margin: 0 auto;
max-width: 280px;
}
.discover-footer {
margin: 40px auto 2px;
max-width: 500px;
}
.discover-feedback {
bottom: 16px;
right: 16px;
text-align: right;
}
.discover-feedback-icon {
padding-bottom: 0.5rem 0.7rem 0.7rem;
&:hover {
color: $blue-500;
}
}
.discover-text-carousel {
max-width: 650px;
.carousel-caption {
height: 100%;
max-width: 500px;
margin-left: auto;
margin-right: auto;
}
}
.carousel-control-next-icon,
.carousel-control-prev-icon {
width: 10px;
@media (min-width: map-get($grid-breakpoints, md)) {
width: inherit;
}
}
.carousel-control-next {
right: -5%;
}
.carousel-control-prev {
left: -5%;
}
.discover-carousel {
margin-bottom: 30px;
.carousel-inner {
width: 90%;
margin-left: auto;
margin-right: auto;
}
.carousel-indicators {
bottom: -20px;
@media (min-width: map-get($grid-breakpoints, lg)) {
bottom: -15px;
}
li {
background-color: $gray-500;
width: 6px;
height: 6px;
border-radius: 100%;
margin-right: 16px;
@media (min-width: map-get($grid-breakpoints, md)) {
width: $gl-spacing-scale-3;
height: $gl-spacing-scale-3;
}
}
.active {
background-color: $gray-500;
}
}
.carousel-control-prev-icon {
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23666666' viewBox='0 0 8 8'%3E%3Cpath d='M5.25 0l-4 4 4 4 1.5-1.5-2.5-2.5 2.5-2.5-1.5-1.5z'/%3E%3C/svg%3E");
}
.carousel-control-next-icon {
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23666666' viewBox='0 0 8 8'%3E%3Cpath d='M2.75 0l-1.5 1.5 2.5 2.5-2.5 2.5 1.5 1.5 4-4-4-4z'/%3E%3C/svg%3E");
}
}
}
- breadcrumb_title _("Security")
- page_title _("Security")
- add_page_specific_style 'page_bundles/security_discover'
- linkFeedback = 'https://gitlab.com/gitlab-org/growth/ui-ux/issues/25'
- linkUpgrade = group_billings_path(@group.root_ancestor)
......
......@@ -4,7 +4,11 @@ module QA
RSpec.describe 'Manage' do
describe 'User with minimal access to group', :requires_admin do
before(:all) do
@user_with_minimal_access = Resource::User.fabricate_via_api!
admin_api_client = Runtime::API::Client.as_admin
@user_with_minimal_access = Resource::User.fabricate_via_api! do |user|
user.api_client = admin_api_client
end
@user_api_client = Runtime::API::Client.new(:gitlab, user: @user_with_minimal_access)
......
......@@ -3,7 +3,13 @@
module QA
RSpec.describe 'Manage' do
describe 'User with minimal access to group', :requires_admin do
let(:user_with_minimal_access) { Resource::User.fabricate_via_api! }
let(:admin_api_client) { Runtime::API::Client.as_admin }
let(:user_with_minimal_access) do
Resource::User.fabricate_via_api! do |user|
user.api_client = admin_api_client
end
end
let(:group) do
group = Resource::Group.fabricate_via_api!
......
......@@ -3,13 +3,6 @@
require 'gitlab'
#
# Configure credentials to be used with gitlab gem
#
Gitlab.configure do |config|
config.endpoint = 'https://gitlab.com/api/v4'
end
module Trigger
def self.ee?
# Support former project name for `dev`
......@@ -34,18 +27,13 @@ module Trigger
ENV['GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN']
end
def initialize
# gitlab-bot's token "GitLab multi-project pipeline polling"
Gitlab.private_token = self.class.access_token
end
def invoke!(post_comment: false, downstream_job_name: nil)
pipeline_variables = variables
puts "Triggering downstream pipeline on #{downstream_project_path}"
puts "with variables #{pipeline_variables}"
pipeline = Gitlab.run_trigger(
pipeline = gitlab_client(:downstream).run_trigger(
downstream_project_path,
trigger_token,
ref,
......@@ -54,23 +42,34 @@ module Trigger
puts "Triggered downstream pipeline: #{pipeline.web_url}\n"
puts "Waiting for downstream pipeline status"
Trigger::CommitComment.post!(pipeline) if post_comment
Trigger::CommitComment.post!(pipeline, gitlab_client(:upstream)) if post_comment
downstream_job =
if downstream_job_name
Gitlab.pipeline_jobs(downstream_project_path, pipeline.id).auto_paginate.find do |potential_job|
gitlab_client(:downstream).pipeline_jobs(downstream_project_path, pipeline.id).auto_paginate.find do |potential_job|
potential_job.name == downstream_job_name
end
end
if downstream_job
Trigger::Job.new(downstream_project_path, downstream_job.id)
Trigger::Job.new(downstream_project_path, downstream_job.id, gitlab_client(:downstream))
else
Trigger::Pipeline.new(downstream_project_path, pipeline.id)
Trigger::Pipeline.new(downstream_project_path, pipeline.id, gitlab_client(:downstream))
end
end
private
# Override to trigger and work with pipeline on different GitLab instance
# type: :downstream -> downstream build and pipeline status
# type: :upstream -> this project, e.g. for posting comments
def gitlab_client(type)
# By default, always use the same client
@gitlab_client ||= Gitlab.client(
endpoint: 'https://gitlab.com/api/v4',
private_token: self.class.access_token
)
end
# Must be overridden
def downstream_project_path
raise NotImplementedError
......@@ -305,9 +304,53 @@ module Trigger
end
end
class DatabaseTesting < Base
def self.access_token
ENV['GITLABCOM_DATABASE_TESTING_ACCESS_TOKEN']
end
private
def gitlab_client(type)
@gitlab_clients ||= {
downstream: Gitlab.client(
endpoint: 'https://ops.gitlab.net/api/v4',
private_token: self.class.access_token
),
upstream: Gitlab.client(
endpoint: 'https://gitlab.com/api/v4',
private_token: Base.access_token
)
}
@gitlab_clients[type]
end
def trigger_token
ENV['GITLABCOM_DATABASE_TESTING_TRIGGER_TOKEN']
end
def downstream_project_path
ENV['GITLABCOM_DATABASE_TESTING_PROJECT_PATH'] || 'gitlab-com/database-team/gitlab-com-database-testing'
end
def extra_variables
{
# Use CI_MERGE_REQUEST_SOURCE_BRANCH_SHA for omnibus checkouts due to pipeline for merged results
# and fallback to CI_COMMIT_SHA for the `detached` pipelines.
'GITLAB_COMMIT_SHA' => Trigger.non_empty_variable_value('CI_MERGE_REQUEST_SOURCE_BRANCH_SHA') || ENV['CI_COMMIT_SHA'],
'TRIGGERED_USER_LOGIN' => ENV['GITLAB_USER_LOGIN']
}
end
def ref
ENV['GITLABCOM_DATABASE_TESTING_TRIGGER_REF'] || 'master'
end
end
class CommitComment
def self.post!(downstream_pipeline)
Gitlab.create_commit_comment(
def self.post!(downstream_pipeline, gitlab_client)
gitlab_client.create_commit_comment(
ENV['CI_PROJECT_PATH'],
Trigger.non_empty_variable_value('CI_MERGE_REQUEST_SOURCE_BRANCH_SHA') || ENV['CI_COMMIT_SHA'],
"The [`#{ENV['CI_JOB_NAME']}`](#{ENV['CI_JOB_URL']}) job from pipeline #{ENV['CI_PIPELINE_URL']} triggered #{downstream_pipeline.web_url} downstream.")
......@@ -329,9 +372,10 @@ module Trigger
unscoped_class_name.downcase
end
def initialize(project, id)
def initialize(project, id, gitlab_client)
@project = project
@id = id
@gitlab_client = gitlab_client
@start_time = Time.now.to_i
end
......@@ -359,7 +403,7 @@ module Trigger
end
def status
Gitlab.public_send(self.class.gitlab_api_method_name, project, id).status.to_sym # rubocop:disable GitlabSecurity/PublicSend
gitlab_client.public_send(self.class.gitlab_api_method_name, project, id).status.to_sym # rubocop:disable GitlabSecurity/PublicSend
rescue Gitlab::Error::Error => error
puts "Ignoring the following error: #{error}"
# Ignore GitLab API hiccups. If GitLab is really down, we'll hit the job
......@@ -369,7 +413,7 @@ module Trigger
private
attr_reader :project, :id, :start_time
attr_reader :project, :id, :gitlab_client, :start_time
end
Job = Class.new(Pipeline)
......@@ -380,6 +424,8 @@ when 'omnibus'
Trigger::Omnibus.new.invoke!(post_comment: true, downstream_job_name: 'Trigger:qa-test').wait!
when 'cng'
Trigger::CNG.new.invoke!.wait!
when 'gitlab-com-database-testing'
Trigger::DatabaseTesting.new.invoke!
when 'docs'
docs_trigger = Trigger::Docs.new
......@@ -395,5 +441,6 @@ when 'docs'
else
puts "Please provide a valid option:
omnibus - Triggers a pipeline that builds the omnibus-gitlab package
cng - Triggers a pipeline that builds images used by the GitLab helm chart"
cng - Triggers a pipeline that builds images used by the GitLab helm chart
gitlab-com-database-testing - Triggers a pipeline that tests database changes on GitLab.com data"
end
......@@ -63,20 +63,53 @@ RSpec.describe Projects::IssuesController do
end
end
describe 'the null hypothesis experiment', :experiment do
before do
stub_experiments(null_hypothesis: :candidate)
end
describe 'the null hypothesis experiment', :snowplow do
it 'defines the expected before actions' do
expect(controller).to use_before_action(:run_null_hypothesis_experiment)
end
it 'assigns the candidate experience and tracks the event' do
expect(experiment(:null_hypothesis)).to track('index').on_any_instance.for(:candidate)
.with_context(project: project)
context 'when rolled out to 100%' do
it 'assigns the candidate experience and tracks the event' do
get :index, params: { namespace_id: project.namespace, project_id: project }
expect_snowplow_event(
category: 'null_hypothesis',
action: 'index',
context: [{
schema: 'iglu:com.gitlab/gitlab_experiment/jsonschema/0-3-0',
data: { variant: 'candidate', experiment: 'null_hypothesis', key: anything }
}]
)
end
end
get :index, params: { namespace_id: project.namespace, project_id: project }
context 'when not rolled out' do
before do
stub_feature_flags(null_hypothesis: false)
end
it 'assigns the control experience and tracks the event' do
get :index, params: { namespace_id: project.namespace, project_id: project }
expect_snowplow_event(
category: 'null_hypothesis',
action: 'index',
context: [{
schema: 'iglu:com.gitlab/gitlab_experiment/jsonschema/0-3-0',
data: { variant: 'control', experiment: 'null_hypothesis', key: anything }
}]
)
end
end
context 'when gitlab_experiments is disabled' do
it 'does not run the experiment at all' do
stub_feature_flags(gitlab_experiments: false)
expect(controller).not_to receive(:run_null_hypothesis_experiment)
get :index, params: { namespace_id: project.namespace, project_id: project }
end
end
end
end
......
......@@ -2,45 +2,9 @@
require 'spec_helper'
RSpec.describe ApplicationExperiment, :experiment do
RSpec.describe ApplicationExperiment do
subject { described_class.new(:stub) }
before do
allow(subject).to receive(:enabled?).and_return(true)
end
describe "enabled" do
before do
allow(subject).to receive(:enabled?).and_call_original
allow(Feature::Definition).to receive(:get).and_return('_instance_')
allow(Gitlab).to receive(:dev_env_or_com?).and_return(true)
allow(Feature).to receive(:get).and_return(double(state: :on))
end
it "is enabled when all criteria are met" do
expect(subject).to be_enabled
end
it "isn't enabled if the feature definition doesn't exist" do
expect(Feature::Definition).to receive(:get).with('stub').and_return(nil)
expect(subject).not_to be_enabled
end
it "isn't enabled if we're not in dev or dotcom environments" do
expect(Gitlab).to receive(:dev_env_or_com?).and_return(false)
expect(subject).not_to be_enabled
end
it "isn't enabled if the feature flag state is :off" do
expect(Feature).to receive(:get).with('stub').and_return(double(state: :off))
expect(subject).not_to be_enabled
end
end
describe "publishing results" do
it "tracks the assignment" do
expect(subject).to receive(:track).with(:assignment)
......@@ -67,8 +31,8 @@ RSpec.describe ApplicationExperiment, :experiment do
end
describe "tracking events", :snowplow do
it "doesn't track if we shouldn't track" do
allow(subject).to receive(:should_track?).and_return(false)
it "doesn't track if excluded" do
subject.exclude { true }
subject.track(:action)
......
# frozen_string_literal: true
# Require the provided spec helper and matchers.
require 'gitlab/experiment/rspec'
# Disable all caching for experiments in tests.
Gitlab::Experiment::Configuration.cache = nil
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment