Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2019-09-24 18:06:05 +00:00
parent f1a5755898
commit 2ed368929a
58 changed files with 720 additions and 123 deletions

View File

@ -1,5 +1,6 @@
extends:
- '@gitlab'
- plugin:promise/recommended
globals:
__webpack_public_path__: true
gl: false
@ -42,6 +43,11 @@ rules:
no-jquery/no-load: error
no-jquery/no-load-shorthand: error
no-jquery/no-serialize: error
promise/always-return: off
promise/no-callback-in-promise: off
promise/no-nesting: off
promise/param-names: off
promise/valid-params: off
overrides:
files:
- '**/spec/**/*'

View File

@ -100,6 +100,7 @@
refs:
- master
- /^\d+-\d+-auto-deploy-\d+$/
- /^[\d-]+-stable(-ee)?$/
.only-review-schedules:
only:

View File

@ -1,8 +1,15 @@
.except-deploys:
except:
refs:
- /^[\d-]+-stable(-ee)?$/
- /^\d+-\d+-auto-deploy-\d+$/
.review-docker:
extends:
- .default-tags
- .default-retry
- .default-only
- .except-deploys
image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-qa-alpine
services:
- docker:19.03.0-dind
@ -36,6 +43,7 @@ schedule:review-cleanup:
- .default-only
- .only-code-qa-changes
- .only-review-schedules
- .except-deploys
stage: prepare
image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-charts-build-base
allow_failure: true
@ -52,6 +60,7 @@ schedule:review-cleanup:
extends:
- .default-only
- .only-code-qa-changes
- .except-deploys
image: ruby:2.6-alpine
stage: review-prepare
before_script:
@ -80,6 +89,7 @@ schedule:review-build-cng:
- .default-retry
- .default-only
- .only-code-qa-changes
- .except-deploys
stage: review
image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-charts-build-base
dependencies: []
@ -257,6 +267,7 @@ parallel-spec-reports:
- .default-only
- .only-code-qa-changes
- .only-review
- .except-deploys
image: ruby:2.6-alpine
stage: post-test
dependencies: ["review-qa-all"]

View File

@ -1,5 +1,8 @@
import Vue from 'vue';
import GlFeatureFlagsPlugin from '~/vue_shared/gl_feature_flags_plugin';
if (process.env.NODE_ENV !== 'production') {
Vue.config.productionTip = false;
}
Vue.use(GlFeatureFlagsPlugin);

View File

@ -175,7 +175,6 @@ export default {
'metricsWithData',
'useDashboardEndpoint',
'allDashboards',
'multipleDashboardsEnabled',
'additionalPanelTypesEnabled',
]),
firstDashboard() {
@ -318,7 +317,6 @@ export default {
<div class="row">
<template v-if="environmentsEndpoint">
<gl-form-group
v-if="multipleDashboardsEnabled"
:label="__('Dashboard')"
label-size="sm"
label-for="monitor-dashboards-dropdown"

View File

@ -14,7 +14,6 @@ export default (props = {}) => {
if (gon.features) {
store.dispatch('monitoringDashboard/setFeatureFlags', {
prometheusEndpointEnabled: gon.features.environmentMetricsUsePrometheusEndpoint,
multipleDashboardsEnabled: gon.features.environmentMetricsShowMultipleDashboards,
additionalPanelTypesEnabled: gon.features.environmentMetricsAdditionalPanelTypes,
});
}

View File

@ -37,10 +37,9 @@ export const setEndpoints = ({ commit }, endpoints) => {
export const setFeatureFlags = (
{ commit },
{ prometheusEndpointEnabled, multipleDashboardsEnabled, additionalPanelTypesEnabled },
{ prometheusEndpointEnabled, additionalPanelTypesEnabled },
) => {
commit(types.SET_DASHBOARD_ENABLED, prometheusEndpointEnabled);
commit(types.SET_MULTIPLE_DASHBOARDS_ENABLED, multipleDashboardsEnabled);
commit(types.SET_ADDITIONAL_PANEL_TYPES_ENABLED, additionalPanelTypesEnabled);
};
@ -51,13 +50,8 @@ export const setShowErrorBanner = ({ commit }, enabled) => {
export const requestMetricsDashboard = ({ commit }) => {
commit(types.REQUEST_METRICS_DATA);
};
export const receiveMetricsDashboardSuccess = (
{ state, commit, dispatch },
{ response, params },
) => {
if (state.multipleDashboardsEnabled) {
commit(types.SET_ALL_DASHBOARDS, response.all_dashboards);
}
export const receiveMetricsDashboardSuccess = ({ commit, dispatch }, { response, params }) => {
commit(types.SET_ALL_DASHBOARDS, response.all_dashboards);
commit(types.RECEIVE_METRICS_DATA_SUCCESS, response.dashboard.panel_groups);
dispatch('fetchPrometheusMetrics', params);
};

View File

@ -10,7 +10,6 @@ export const RECEIVE_ENVIRONMENTS_DATA_FAILURE = 'RECEIVE_ENVIRONMENTS_DATA_FAIL
export const SET_QUERY_RESULT = 'SET_QUERY_RESULT';
export const SET_TIME_WINDOW = 'SET_TIME_WINDOW';
export const SET_DASHBOARD_ENABLED = 'SET_DASHBOARD_ENABLED';
export const SET_MULTIPLE_DASHBOARDS_ENABLED = 'SET_MULTIPLE_DASHBOARDS_ENABLED';
export const SET_ADDITIONAL_PANEL_TYPES_ENABLED = 'SET_ADDITIONAL_PANEL_TYPES_ENABLED';
export const SET_ALL_DASHBOARDS = 'SET_ALL_DASHBOARDS';
export const SET_ENDPOINTS = 'SET_ENDPOINTS';

View File

@ -89,9 +89,6 @@ export default {
[types.SET_DASHBOARD_ENABLED](state, enabled) {
state.useDashboardEndpoint = enabled;
},
[types.SET_MULTIPLE_DASHBOARDS_ENABLED](state, enabled) {
state.multipleDashboardsEnabled = enabled;
},
[types.SET_GETTING_STARTED_EMPTY_STATE](state) {
state.emptyState = 'gettingStarted';
},

View File

@ -8,7 +8,6 @@ export default () => ({
deploymentsEndpoint: null,
dashboardEndpoint: invalidUrl,
useDashboardEndpoint: false,
multipleDashboardsEnabled: false,
additionalPanelTypesEnabled: false,
emptyState: 'gettingStarted',
showEmptyState: true,

View File

@ -7,6 +7,7 @@ import _ from 'underscore';
import axios from './lib/utils/axios_utils';
import { s__, __, sprintf } from './locale';
import ModalStore from './boards/stores/modal_store';
import { parseBoolean } from './lib/utils/common_utils';
// TODO: remove eventHub hack after code splitting refactor
window.emitSidebarEvent = window.emitSidebarEvent || $.noop;
@ -275,12 +276,13 @@ function UsersSelect(currentUser, els, options = {}) {
})
.map(input => {
const userId = parseInt(input.value, 10);
const { avatarUrl, avatar_url, name, username } = input.dataset;
const { avatarUrl, avatar_url, name, username, canMerge } = input.dataset;
return {
avatar_url: avatarUrl || avatar_url,
id: userId,
name,
username,
can_merge: parseBoolean(canMerge),
};
});

View File

@ -1,5 +1,7 @@
<script>
import $ from 'jquery';
import { __ } from '~/locale';
import createFlash from '~/flash';
import statusIcon from '../mr_widget_status_icon.vue';
import tooltip from '../../../vue_shared/directives/tooltip';
import eventHub from '../../event_hub';
@ -29,12 +31,12 @@ export default {
.then(res => res.data)
.then(data => {
eventHub.$emit('UpdateWidgetData', data);
new window.Flash('The merge request can now be merged.', 'notice'); // eslint-disable-line
createFlash(__('The merge request can now be merged.'), 'notice');
$('.merge-request .detail-page-description .title').text(this.mr.title);
})
.catch(() => {
this.isMakingRequest = false;
new window.Flash('Something went wrong. Please try again.'); // eslint-disable-line
createFlash(__('Something went wrong. Please try again.'));
});
},
},

View File

@ -0,0 +1,7 @@
export default Vue => {
Vue.mixin({
provide: {
glFeatures: { ...((window.gon && window.gon.features) || {}) },
},
});
};

View File

@ -0,0 +1,8 @@
export default () => ({
inject: {
glFeatures: {
from: 'glFeatures',
default: () => ({}),
},
},
});

View File

@ -245,14 +245,6 @@ $note-form-margin-left: 72px;
}
}
.note-header {
@include notes-media('max', map-get($grid-breakpoints, xs)) {
.inline {
display: block;
}
}
}
.note-emoji-button {
position: relative;
line-height: 1;
@ -635,10 +627,6 @@ $note-form-margin-left: 72px;
.note-headline-light {
display: inline;
@include notes-media('max', map-get($grid-breakpoints, xs)) {
display: block;
}
}
.note-headline-light,

View File

@ -0,0 +1,7 @@
# frozen_string_literal: true
module RendersAssignees
def preload_assignees_for_render(merge_request)
merge_request.project.team.max_member_access_for_user_ids(merge_request.assignees.map(&:id))
end
end

View File

@ -12,7 +12,6 @@ class Projects::EnvironmentsController < Projects::ApplicationController
before_action :expire_etag_cache, only: [:index]
before_action only: [:metrics, :additional_metrics, :metrics_dashboard] do
push_frontend_feature_flag(:environment_metrics_use_prometheus_endpoint, default_enabled: true)
push_frontend_feature_flag(:environment_metrics_show_multiple_dashboards)
push_frontend_feature_flag(:environment_metrics_additional_panel_types)
push_frontend_feature_flag(:prometheus_computed_alerts)
end
@ -168,7 +167,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController
dashboard_path: params[:dashboard],
**dashboard_params.to_h.symbolize_keys
)
elsif Feature.enabled?(:environment_metrics_show_multiple_dashboards, project)
else
result = dashboard_finder.find(
project,
current_user,
@ -177,8 +176,6 @@ class Projects::EnvironmentsController < Projects::ApplicationController
)
result[:all_dashboards] = dashboard_finder.find_all_paths(project)
else
result = dashboard_finder.find(project, current_user, environment: environment)
end
respond_to do |format|

View File

@ -5,6 +5,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
include IssuableActions
include RendersNotes
include RendersCommits
include RendersAssignees
include ToggleAwardEmoji
include IssuableCollections
include RecordUserLastActivity
@ -41,6 +42,8 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
# use next to appease Rubocop
next render('invalid') if target_branch_missing?
preload_assignees_for_render(@merge_request)
# Build a note object for comment form
@note = @project.notes.new(noteable: @merge_request)

View File

@ -372,6 +372,12 @@ module IssuablesHelper
finder.class.scalar_params.any? { |p| params[p].present? }
end
def assignee_sidebar_data(assignee, merge_request: nil)
{ avatar_url: assignee.avatar_url, name: assignee.name, username: assignee.username }.tap do |data|
data[:can_merge] = merge_request.can_be_merged_by?(assignee) if merge_request
end
end
private
def sidebar_gutter_collapsed?

View File

@ -40,7 +40,7 @@
.info-well
.well-segment.admin-well.admin-well-features
%h4 Features
= feature_entry(_('Sign up'), href: admin_application_settings_path(anchor: 'js-signup-settings'))
= feature_entry(_('Sign up'), href: admin_application_settings_path(anchor: 'js-signup-settings'), enabled: allow_signup?)
= feature_entry(_('LDAP'), enabled: Gitlab.config.ldap.enabled)
= feature_entry(_('Gravatar'), href: admin_application_settings_path(anchor: 'js-account-settings'), enabled: gravatar_enabled?)
= feature_entry(_('OmniAuth'), href: admin_application_settings_path(anchor: 'js-signin-settings'), enabled: Gitlab::Auth.omniauth_enabled?)

View File

@ -11,7 +11,7 @@
= hidden_field_tag "#{issuable_type}[assignee_ids][]", 0, id: nil
- else
- assignees.each do |assignee|
= hidden_field_tag "#{issuable_type}[assignee_ids][]", assignee.id, id: nil, data: { avatar_url: assignee.avatar_url, name: assignee.name, username: assignee.username }
= hidden_field_tag "#{issuable_type}[assignee_ids][]", assignee.id, id: nil, data: assignee_sidebar_data(assignee, merge_request: @merge_request)
- options = { toggle_class: 'js-user-search js-author-search',
title: _('Assign to'),

View File

@ -0,0 +1,5 @@
---
title: Remove map-get($grid-breakpoints, xs) for max-width
merge_request: 17420
author: Takuya Noguchi
type: other

View File

@ -0,0 +1,5 @@
---
title: Fix cannot merge icon showing in dropdown for users who can merge
merge_request: 17306
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Fix signup link in admin area not being disabled
merge_request: 16726
author: Illya Klymov
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Add allowFilter and allowAnySHA1InWant for partial clones
merge_request: 16850
author:
type: added

View File

@ -0,0 +1,5 @@
---
title: Add timeout mechanism for CI config validation
merge_request: 16807
author:
type: fixed

View File

@ -2289,6 +2289,10 @@ Nested includes allow you to compose a set of includes.
A total of 50 includes is allowed.
Duplicate includes are considered a configuration error.
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/28212) in GitLab 12.4.
A hard limit of 30 seconds was set for resolving all files.
#### `include` examples
Here are a few more `include` examples.

View File

@ -104,6 +104,51 @@ document.addEventListener('DOMContentLoaded', () => new Vue({
}));
```
#### Accessing feature flags
Use Vue's [provide/inject](https://vuejs.org/v2/api/#provide-inject) mechanism
to make feature flags available to any descendant components in a Vue
application. The `glFeatures` object is already provided in `commons/vue.js`, so
only the mixin is required to utilize the flags:
```javascript
// An arbitrary descendant component
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
export default {
// ...
mixins: [glFeatureFlagsMixin()],
// ...
created() {
if (this.glFeatures.myFlag) {
// ...
}
},
}
```
This approach has a few benefits:
- Arbitrarily deeply nested components can opt-in and access the flag without
intermediate components being aware of it (c.f. passing the flag down via
props).
- Good testability, since the flag can be provided to `mount`/`shallowMount`
from `vue-test-utils` as easily as a prop.
```javascript
import { shallowMount } from '@vue/test-utils';
shallowMount(component, {
provide: {
glFeatures: { myFlag: true },
},
});
```
- No need to access a global variable, except in the application's
[entry point](#accessing-the-gl-object).
### A folder for Components
This folder holds all components that are specific of this new feature.

View File

@ -109,6 +109,9 @@ if ( gon.features.vimBindings ) {
The name of the feature flag in JavaScript will always be camelCased, meaning
that checking for `gon.features.vim_bindings` would not work.
See the [Vue guide](../fe_guide/vue.md#accessing-feature-flags) for details about
how to access feature flags in a Vue component.
### Specs
In the test environment `Feature.enabled?` is stubbed to always respond to `true`,

View File

@ -78,6 +78,10 @@ module API
receive_max_input_size = Gitlab::CurrentSettings.receive_max_input_size.to_i
if receive_max_input_size > 0
payload[:git_config_options] << "receive.maxInputSize=#{receive_max_input_size.megabytes}"
if Feature.enabled?(:gitaly_upload_pack_filter, project)
payload[:git_config_options] << "uploadpack.allowFilter=true" << "uploadpack.allowAnySHA1InWant=true"
end
end
response_with_status(**payload)

View File

@ -7,6 +7,8 @@ module Gitlab
#
class Config
ConfigError = Class.new(StandardError)
TIMEOUT_SECONDS = 30.seconds
TIMEOUT_MESSAGE = 'Resolving config took longer than expected'
RESCUE_ERRORS = [
Gitlab::Config::Loader::FormatError,
@ -17,17 +19,17 @@ module Gitlab
attr_reader :root
def initialize(config, project: nil, sha: nil, user: nil)
@config = Config::Extendable
.new(build_config(config, project: project, sha: sha, user: user))
.to_hash
@context = build_context(project: project, sha: sha, user: user)
if Feature.enabled?(:ci_limit_yaml_expansion, project, default_enabled: true)
@context.set_deadline(TIMEOUT_SECONDS)
end
@config = expand_config(config)
@root = Entry::Root.new(@config)
@root.compose!
rescue Gitlab::Config::Loader::Yaml::DataTooLargeError => e
Gitlab::Sentry.track_exception(e, extra: { user: user.inspect, project: project.inspect })
raise Config::ConfigError, e.message
rescue *rescue_errors => e
raise Config::ConfigError, e.message
end
@ -61,18 +63,34 @@ module Gitlab
private
def build_config(config, project:, sha:, user:)
initial_config = Gitlab::Config::Loader::Yaml.new(config).load!
def expand_config(config)
build_config(config)
process_external_files(initial_config, project: project, sha: sha, user: user)
rescue Gitlab::Config::Loader::Yaml::DataTooLargeError => e
track_exception(e)
raise Config::ConfigError, e.message
rescue Gitlab::Ci::Config::External::Context::TimeoutError => e
track_exception(e)
raise Config::ConfigError, TIMEOUT_MESSAGE
end
def process_external_files(config, project:, sha:, user:)
Config::External::Processor.new(config,
def build_config(config)
initial_config = Gitlab::Config::Loader::Yaml.new(config).load!
initial_config = Config::External::Processor.new(initial_config, @context).perform
Config::Extendable.new(initial_config).to_hash
end
def build_context(project:, sha:, user:)
Config::External::Context.new(
project: project,
sha: sha || project&.repository&.root_ref_sha,
user: user,
expandset: Set.new).perform
user: user)
end
def track_exception(error)
Gitlab::Sentry.track_exception(error, extra: @context.sentry_payload)
end
# Overriden in EE

View File

@ -0,0 +1,64 @@
# frozen_string_literal: true
module Gitlab
module Ci
class Config
module External
class Context
TimeoutError = Class.new(StandardError)
attr_reader :project, :sha, :user
attr_reader :expandset, :execution_deadline
def initialize(project: nil, sha: nil, user: nil)
@project = project
@sha = sha
@user = user
@expandset = Set.new
@execution_deadline = 0
yield self if block_given?
end
def mutate(attrs = {})
self.class.new(**attrs) do |ctx|
ctx.expandset = expandset
ctx.execution_deadline = execution_deadline
end
end
def set_deadline(timeout_seconds)
@execution_deadline = current_monotonic_time + timeout_seconds.to_f
end
def check_execution_time!
raise TimeoutError if execution_expired?
end
def sentry_payload
{
user: user.inspect,
project: project.inspect
}
end
protected
attr_writer :expandset, :execution_deadline
private
def current_monotonic_time
Gitlab::Metrics::System.monotonic_time
end
def execution_expired?
return false if execution_deadline.zero?
current_monotonic_time > execution_deadline
end
end
end
end
end
end

View File

@ -12,8 +12,6 @@ module Gitlab
YAML_WHITELIST_EXTENSION = /.+\.(yml|yaml)$/i.freeze
Context = Struct.new(:project, :sha, :user, :expandset)
def initialize(params, context)
@params = params
@context = context
@ -69,11 +67,16 @@ module Gitlab
end
def validate!
validate_execution_time!
validate_location!
validate_content! if errors.none?
validate_hash! if errors.none?
end
def validate_execution_time!
context.check_execution_time!
end
def validate_location!
if invalid_location_type?
errors.push("Included file `#{location}` needs to be a string")
@ -95,11 +98,11 @@ module Gitlab
end
def expand_includes(hash)
External::Processor.new(hash, **expand_context).perform
External::Processor.new(hash, context.mutate(expand_context_attrs)).perform
end
def expand_context
{ project: nil, sha: nil, user: nil, expandset: context.expandset }
def expand_context_attrs
{}
end
end
end

View File

@ -6,6 +6,7 @@ module Gitlab
module External
module File
class Local < Base
extend ::Gitlab::Utils::Override
include Gitlab::Utils::StrongMemoize
def initialize(params, context)
@ -34,11 +35,13 @@ module Gitlab
context.project.repository.blob_data_at(context.sha, location)
end
def expand_context
super.merge(
override :expand_context_attrs
def expand_context_attrs
{
project: context.project,
sha: context.sha,
user: context.user)
user: context.user
}
end
end
end

View File

@ -6,11 +6,12 @@ module Gitlab
module External
module File
class Project < Base
extend ::Gitlab::Utils::Override
include Gitlab::Utils::StrongMemoize
attr_reader :project_name, :ref_name
def initialize(params, context = {})
def initialize(params, context)
@location = params[:file]
@project_name = params[:project]
@ref_name = params[:ref] || 'HEAD'
@ -65,11 +66,13 @@ module Gitlab
end
end
def expand_context
super.merge(
override :expand_context_attrs
def expand_context_attrs
{
project: project,
sha: sha,
user: context.user)
user: context.user
}
end
end
end

View File

@ -21,14 +21,9 @@ module Gitlab
DuplicateIncludesError = Class.new(Error)
TooManyIncludesError = Class.new(Error)
def initialize(values, project:, sha:, user:, expandset:)
raise Error, 'Expanded needs to be `Set`' unless expandset.is_a?(Set)
def initialize(values, context)
@locations = Array.wrap(values.fetch(:include, []))
@project = project
@sha = sha
@user = user
@expandset = expandset
@context = context
end
def process
@ -43,7 +38,9 @@ module Gitlab
private
attr_reader :locations, :project, :sha, :user, :expandset
attr_reader :locations, :context
delegate :expandset, to: :context
# convert location if String to canonical form
def normalize_location(location)
@ -68,11 +65,11 @@ module Gitlab
end
# We scope location to context, as this allows us to properly support
# relative incldues, and similarly looking relative in another project
# relative includes, and similarly looking relative in another project
# does not trigger duplicate error
scoped_location = location.merge(
context_project: project,
context_sha: sha)
context_project: context.project,
context_sha: context.sha)
unless expandset.add?(scoped_location)
raise DuplicateIncludesError, "Include `#{location.to_json}` was already included!"
@ -88,12 +85,6 @@ module Gitlab
matching.first
end
def context
strong_memoize(:context) do
External::File::Base::Context.new(project, sha, user, expandset)
end
end
end
end
end

View File

@ -7,9 +7,9 @@ module Gitlab
class Processor
IncludeError = Class.new(StandardError)
def initialize(values, project:, sha:, user:, expandset:)
def initialize(values, context)
@values = values
@external_files = External::Mapper.new(values, project: project, sha: sha, user: user, expandset: expandset).process
@external_files = External::Mapper.new(values, context).process
@content = {}
rescue External::Mapper::Error,
OpenSSL::SSL::SSLError => e

View File

@ -15527,6 +15527,9 @@ msgstr ""
msgid "The merge conflicts for this merge request have already been resolved. Please return to the merge request."
msgstr ""
msgid "The merge request can now be merged."
msgstr ""
msgid "The name %{entryName} is already taken in this directory."
msgstr ""

View File

@ -483,10 +483,8 @@ describe Projects::EnvironmentsController do
end
shared_examples_for 'the default dashboard' do
all_dashboards = Feature.enabled?(:environment_metrics_show_multiple_dashboards)
it_behaves_like '200 response'
it_behaves_like 'includes all dashboards' if all_dashboards
it_behaves_like 'includes all dashboards'
it 'is the default dashboard' do
get :metrics_dashboard, params: environment_params(dashboard_params)
@ -618,16 +616,6 @@ describe Projects::EnvironmentsController do
it_behaves_like 'the default dashboard'
it_behaves_like 'dashboard can be specified'
it_behaves_like 'dashboard can be embedded'
context 'when multiple dashboards is disabled' do
before do
stub_feature_flags(environment_metrics_show_multiple_dashboards: false)
end
it_behaves_like 'the default dashboard'
it_behaves_like 'dashboard cannot be specified'
it_behaves_like 'dashboard can be embedded'
end
end
describe 'GET #search' do

View File

@ -0,0 +1,68 @@
# frozen_string_literal: true
require 'spec_helper'
describe 'Merge request > User edits assignees sidebar', :js do
let(:project) { create(:project, :public, :repository) }
let(:protected_branch) { create(:protected_branch, :maintainers_can_push, name: 'master', project: project) }
let(:merge_request) { create(:merge_request, :simple, source_project: project, target_branch: protected_branch.name) }
let(:users_find_limit) { 5 }
# Insert more than limit so that response doesn't include assigned user
let(:project_developers) { Array.new(users_find_limit + 1) { create(:user).tap { |u| project.add_developer(u) } } }
let(:project_maintainers) { Array.new(users_find_limit + 1) { create(:user).tap { |u| project.add_maintainer(u) } } }
# DOM finders to simplify and improve readability
let(:sidebar_assignee_block) { page.find('.js-issuable-sidebar .assignee') }
let(:sidebar_assignee_avatar_link) { sidebar_assignee_block.find_all('a').find { |a| a['href'].include? assignee.username } }
let(:sidebar_assignee_tooltip) { sidebar_assignee_avatar_link['data-original-title'] || '' }
let(:sidebar_assignee_dropdown_item) { sidebar_assignee_block.find(".dropdown-menu li[data-user-id=\"#{assignee.id}\"]") }
let(:sidebar_assignee_dropdown_tooltip) { sidebar_assignee_dropdown_item.find('a')['data-title'] || '' }
before do
stub_const('Autocomplete::UsersFinder::LIMIT', users_find_limit)
sign_in(project.owner)
merge_request.assignees << assignee
visit project_merge_request_path(project, merge_request)
wait_for_requests
end
shared_examples 'when assigned' do |expected_tooltip: ''|
it 'shows assignee name' do
expect(sidebar_assignee_block).to have_text(assignee.name)
end
it "shows assignee tooltip '#{expected_tooltip}'" do
expect(sidebar_assignee_tooltip).to eql(expected_tooltip)
end
context 'when edit is clicked' do
before do
sidebar_assignee_block.click_link('Edit')
wait_for_requests
end
it "shows assignee tooltip '#{expected_tooltip}" do
expect(sidebar_assignee_dropdown_tooltip).to eql(expected_tooltip)
end
end
end
context 'when assigned to maintainer' do
let(:assignee) { project_maintainers.last }
it_behaves_like 'when assigned', expected_tooltip: ''
end
context 'when assigned to developer' do
let(:assignee) { project_developers.last }
it_behaves_like 'when assigned', expected_tooltip: 'Cannot merge'
end
end

View File

@ -0,0 +1,42 @@
import { createLocalVue, shallowMount } from '@vue/test-utils';
import GlFeatureFlags from '~/vue_shared/gl_feature_flags_plugin';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
const localVue = createLocalVue();
describe('GitLab Feature Flags Plugin', () => {
beforeEach(() => {
window.gon = {
features: {
aFeature: true,
bFeature: false,
},
};
localVue.use(GlFeatureFlags);
});
it('should provide glFeatures to components', () => {
const component = {
template: `<span></span>`,
inject: ['glFeatures'],
};
const wrapper = shallowMount(component, { localVue });
expect(wrapper.vm.glFeatures).toEqual({
aFeature: true,
bFeature: false,
});
});
it('should integrate with the glFeatureMixin', () => {
const component = {
template: `<span></span>`,
mixins: [glFeatureFlagsMixin()],
};
const wrapper = shallowMount(component, { localVue });
expect(wrapper.vm.glFeatures).toEqual({
aFeature: true,
bFeature: false,
});
});
});

View File

@ -0,0 +1,36 @@
import { createLocalVue, shallowMount } from '@vue/test-utils';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
const localVue = createLocalVue();
describe('GitLab Feature Flags Mixin', () => {
let wrapper;
beforeEach(() => {
const gon = {
features: {
aFeature: true,
bFeature: false,
},
};
const component = {
template: `<span></span>`,
mixins: [glFeatureFlagsMixin()],
};
wrapper = shallowMount(component, {
localVue,
provide: {
glFeatures: { ...(gon.features || {}) },
},
});
});
it('should provide glFeatures to components', () => {
expect(wrapper.vm.glFeatures).toEqual({
aFeature: true,
bFeature: false,
});
});
});

View File

@ -243,4 +243,32 @@ describe IssuablesHelper do
end
end
end
describe '#assignee_sidebar_data' do
let(:user) { create(:user) }
let(:merge_request) { nil }
subject { helper.assignee_sidebar_data(user, merge_request: merge_request) }
it 'returns hash of assignee data' do
is_expected.to eql({
avatar_url: user.avatar_url,
name: user.name,
username: user.username
})
end
context 'with merge_request' do
let(:merge_request) { build_stubbed(:merge_request) }
where(can_merge: [true, false])
with_them do
before do
allow(merge_request).to receive(:can_be_merged_by?).and_return(can_merge)
end
it { is_expected.to include({ can_merge: can_merge })}
end
end
end
end

View File

@ -4,6 +4,7 @@ import { createStore } from '~/create_cluster/gke_cluster/store';
import { SET_PROJECTS } from '~/create_cluster/gke_cluster/store/mutation_types';
import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import { emptyProjectMock, selectedProjectMock } from '../mock_data';
import { gapi } from '../helpers';
const componentConfig = {
docsUrl: 'https://console.cloud.google.com/home/dashboard',
@ -32,6 +33,16 @@ describe('GkeProjectIdDropdown', () => {
let vm;
let store;
let originalGapi;
beforeAll(() => {
originalGapi = window.gapi;
window.gapi = gapi();
});
afterAll(() => {
window.gapi = originalGapi;
});
beforeEach(() => {
store = createStore();
vm = createComponent(store);

View File

@ -64,7 +64,15 @@ describe('GCP Cluster Dropdown Store Actions', () => {
});
describe('async fetch methods', () => {
window.gapi = gapi();
let originalGapi;
beforeAll(() => {
originalGapi = window.gapi;
window.gapi = gapi();
});
afterAll(() => {
window.gapi = originalGapi;
});
describe('fetchProjects', () => {
it('fetches projects from Google API', done => {

View File

@ -527,7 +527,6 @@ describe('Dashboard', () => {
component.$store.dispatch('monitoringDashboard/setFeatureFlags', {
prometheusEndpoint: false,
multipleDashboardsEnabled: true,
});
component.$store.commit(

View File

@ -240,8 +240,6 @@ describe('Monitoring store actions', () => {
const response = metricsDashboardResponse;
response.all_dashboards = dashboardGitResponse;
state.multipleDashboardsEnabled = true;
receiveMetricsDashboardSuccess({ state, commit, dispatch }, { response, params });
expect(commit).toHaveBeenCalledWith(types.SET_ALL_DASHBOARDS, dashboardGitResponse);

View File

@ -47,7 +47,7 @@ describe('Wip', () => {
it('should make a request to service and handle response', done => {
const vm = createComponent();
spyOn(window, 'Flash').and.returnValue(true);
const flashSpy = spyOnDependency(WorkInProgress, 'createFlash').and.returnValue(true);
spyOn(eventHub, '$emit');
spyOn(vm.service, 'removeWIP').and.returnValue(
new Promise(resolve => {
@ -61,10 +61,7 @@ describe('Wip', () => {
setTimeout(() => {
expect(vm.isMakingRequest).toBeTruthy();
expect(eventHub.$emit).toHaveBeenCalledWith('UpdateWidgetData', mrObj);
expect(window.Flash).toHaveBeenCalledWith(
'The merge request can now be merged.',
'notice',
);
expect(flashSpy).toHaveBeenCalledWith('The merge request can now be merged.', 'notice');
done();
}, 333);
});

View File

@ -0,0 +1,128 @@
# frozen_string_literal: true
require 'fast_spec_helper'
describe Gitlab::Ci::Config::External::Context do
let(:project) { double('Project') }
let(:user) { double('User') }
let(:sha) { '12345' }
let(:attributes) { { project: project, user: user, sha: sha } }
subject(:subject) { described_class.new(**attributes) }
describe 'attributes' do
context 'with values' do
it { is_expected.to have_attributes(**attributes) }
it { expect(subject.expandset).to eq(Set.new) }
it { expect(subject.execution_deadline).to eq(0) }
end
context 'without values' do
let(:attributes) { { project: nil, user: nil, sha: nil } }
it { is_expected.to have_attributes(**attributes) }
it { expect(subject.expandset).to eq(Set.new) }
it { expect(subject.execution_deadline).to eq(0) }
end
end
describe '#set_deadline' do
let(:stubbed_time) { 1 }
before do
allow(subject).to receive(:current_monotonic_time).and_return(stubbed_time)
end
context 'with a float value' do
let(:timeout_seconds) { 10.5.seconds }
it 'updates execution_deadline' do
expect { subject.set_deadline(timeout_seconds) }
.to change { subject.execution_deadline }
.to(timeout_seconds + stubbed_time)
end
end
context 'with nil as a value' do
let(:timeout_seconds) {}
it 'updates execution_deadline' do
expect { subject.set_deadline(timeout_seconds) }
.to change { subject.execution_deadline }
.to(stubbed_time)
end
end
end
describe '#check_execution_time!' do
before do
allow(subject).to receive(:current_monotonic_time).and_return(stubbed_time)
allow(subject).to receive(:execution_deadline).and_return(stubbed_deadline)
end
context 'when execution is expired' do
let(:stubbed_time) { 2 }
let(:stubbed_deadline) { 1 }
it 'raises an error' do
expect { subject.check_execution_time! }
.to raise_error(described_class::TimeoutError)
end
end
context 'when execution is not expired' do
let(:stubbed_time) { 1 }
let(:stubbed_deadline) { 2 }
it 'does not raises any errors' do
expect { subject.check_execution_time! }.not_to raise_error
end
end
context 'without setting a deadline' do
let(:stubbed_time) { 2 }
let(:stubbed_deadline) { 1 }
before do
allow(subject).to receive(:execution_deadline).and_call_original
end
it 'does not raises any errors' do
expect { subject.check_execution_time! }.not_to raise_error
end
end
end
describe '#mutate' do
shared_examples 'a mutated context' do
let(:mutated) { subject.mutate(new_attributes) }
before do
subject.expandset << :a_file
subject.set_deadline(15.seconds)
end
it { expect(mutated).not_to eq(subject) }
it { expect(mutated).to be_a(described_class) }
it { expect(mutated).to have_attributes(new_attributes) }
it { expect(mutated.expandset).to eq(subject.expandset) }
it { expect(mutated.execution_deadline).to eq(mutated.execution_deadline) }
end
context 'with attributes' do
let(:new_attributes) { { project: double, user: double, sha: '56789' } }
it_behaves_like 'a mutated context'
end
context 'without attributes' do
let(:new_attributes) { {} }
it_behaves_like 'a mutated context'
end
end
describe '#sentry_payload' do
it { expect(subject.sentry_payload).to match(a_hash_including(:project, :user)) }
end
end

View File

@ -1,13 +1,14 @@
# frozen_string_literal: true
require 'fast_spec_helper'
require 'spec_helper'
describe Gitlab::Ci::Config::External::File::Base do
let(:context) { described_class::Context.new(nil, 'HEAD', nil, Set.new) }
let(:context_params) { { sha: 'HEAD' } }
let(:context) { Gitlab::Ci::Config::External::Context.new(**context_params) }
let(:test_class) do
Class.new(described_class) do
def initialize(params, context = {})
def initialize(params, context)
@location = params
super
@ -20,6 +21,9 @@ describe Gitlab::Ci::Config::External::File::Base do
before do
allow_any_instance_of(test_class)
.to receive(:content).and_return('key: value')
allow_any_instance_of(Gitlab::Ci::Config::External::Context)
.to receive(:check_execution_time!)
end
describe '#matching?' do

View File

@ -7,10 +7,17 @@ describe Gitlab::Ci::Config::External::File::Local do
set(:user) { create(:user) }
let(:sha) { '12345' }
let(:context) { described_class::Context.new(project, sha, user, Set.new) }
let(:context_params) { { project: project, sha: sha, user: user } }
let(:context) { Gitlab::Ci::Config::External::Context.new(**context_params) }
let(:params) { { local: location } }
let(:local_file) { described_class.new(params, context) }
before do
allow_any_instance_of(Gitlab::Ci::Config::External::Context)
.to receive(:check_execution_time!)
end
describe '#matching?' do
context 'when a local is specified' do
let(:params) { { local: 'file' } }
@ -109,7 +116,7 @@ describe Gitlab::Ci::Config::External::File::Local do
describe '#expand_context' do
let(:location) { 'location.yml' }
subject { local_file.send(:expand_context) }
subject { local_file.send(:expand_context_attrs) }
it 'inherits project, user and sha' do
is_expected.to include(user: user, project: project, sha: sha)

View File

@ -8,11 +8,15 @@ describe Gitlab::Ci::Config::External::File::Project do
set(:user) { create(:user) }
let(:context_user) { user }
let(:context) { described_class::Context.new(context_project, '12345', context_user, Set.new) }
let(:context_params) { { project: context_project, sha: '12345', user: context_user } }
let(:context) { Gitlab::Ci::Config::External::Context.new(**context_params) }
let(:project_file) { described_class.new(params, context) }
before do
project.add_developer(user)
allow_any_instance_of(Gitlab::Ci::Config::External::Context)
.to receive(:check_execution_time!)
end
describe '#matching?' do
@ -145,7 +149,7 @@ describe Gitlab::Ci::Config::External::File::Project do
describe '#expand_context' do
let(:params) { { file: 'file.yml', project: project.full_path, ref: 'master' } }
subject { project_file.send(:expand_context) }
subject { project_file.send(:expand_context_attrs) }
it 'inherits user, and target project and sha' do
is_expected.to include(user: user, project: project, sha: project.commit('master').id)

View File

@ -5,7 +5,8 @@ require 'spec_helper'
describe Gitlab::Ci::Config::External::File::Remote do
include StubRequests
let(:context) { described_class::Context.new(nil, '12345', nil, Set.new) }
let(:context_params) { { sha: '12345' } }
let(:context) { Gitlab::Ci::Config::External::Context.new(**context_params) }
let(:params) { { remote: location } }
let(:remote_file) { described_class.new(params, context) }
let(:location) { 'https://gitlab.com/gitlab-org/gitlab-foss/blob/1234/.gitlab-ci-1.yml' }
@ -19,6 +20,11 @@ describe Gitlab::Ci::Config::External::File::Remote do
HEREDOC
end
before do
allow_any_instance_of(Gitlab::Ci::Config::External::Context)
.to receive(:check_execution_time!)
end
describe '#matching?' do
context 'when a remote is specified' do
let(:params) { { remote: 'http://remote' } }
@ -187,10 +193,10 @@ describe Gitlab::Ci::Config::External::File::Remote do
describe '#expand_context' do
let(:params) { { remote: 'http://remote' } }
subject { remote_file.send(:expand_context) }
subject { remote_file.send(:expand_context_attrs) }
it 'drops all parameters' do
is_expected.to include(user: nil, project: nil, sha: nil)
is_expected.to be_empty
end
end
end

View File

@ -6,12 +6,18 @@ describe Gitlab::Ci::Config::External::File::Template do
set(:project) { create(:project) }
set(:user) { create(:user) }
let(:context) { described_class::Context.new(project, '12345', user, Set.new) }
let(:context_params) { { project: project, sha: '12345', user: user } }
let(:context) { Gitlab::Ci::Config::External::Context.new(**context_params) }
let(:template) { 'Auto-DevOps.gitlab-ci.yml' }
let(:params) { { template: template } }
let(:template_file) { described_class.new(params, context) }
before do
allow_any_instance_of(Gitlab::Ci::Config::External::Context)
.to receive(:check_execution_time!)
end
describe '#matching?' do
context 'when a template is specified' do
let(:params) { { template: 'some-template' } }
@ -97,10 +103,10 @@ describe Gitlab::Ci::Config::External::File::Template do
describe '#expand_context' do
let(:location) { 'location.yml' }
subject { template_file.send(:expand_context) }
subject { template_file.send(:expand_context_attrs) }
it 'drops all parameters' do
is_expected.to include(user: nil, project: nil, sha: nil)
is_expected.to be_empty
end
end
end

View File

@ -11,7 +11,8 @@ describe Gitlab::Ci::Config::External::Mapper do
let(:local_file) { '/lib/gitlab/ci/templates/non-existent-file.yml' }
let(:remote_url) { 'https://gitlab.com/gitlab-org/gitlab-foss/blob/1234/.gitlab-ci-1.yml' }
let(:template_file) { 'Auto-DevOps.gitlab-ci.yml' }
let(:expandset) { Set.new }
let(:context_params) { { project: project, sha: '123456', user: user } }
let(:context) { Gitlab::Ci::Config::External::Context.new(**context_params) }
let(:file_content) do
<<~HEREDOC
@ -21,10 +22,13 @@ describe Gitlab::Ci::Config::External::Mapper do
before do
stub_full_request(remote_url).to_return(body: file_content)
allow_any_instance_of(Gitlab::Ci::Config::External::Context)
.to receive(:check_execution_time!)
end
describe '#process' do
subject { described_class.new(values, project: project, sha: '123456', user: user, expandset: expandset).process }
subject { described_class.new(values, context).process }
context "when single 'include' keyword is defined" do
context 'when the string is a local file' do

View File

@ -9,12 +9,16 @@ describe Gitlab::Ci::Config::External::Processor do
set(:another_project) { create(:project, :repository) }
set(:user) { create(:user) }
let(:expandset) { Set.new }
let(:sha) { '12345' }
let(:processor) { described_class.new(values, project: project, sha: '12345', user: user, expandset: expandset) }
let(:context_params) { { project: project, sha: sha, user: user } }
let(:context) { Gitlab::Ci::Config::External::Context.new(**context_params) }
let(:processor) { described_class.new(values, context) }
before do
project.add_developer(user)
allow_any_instance_of(Gitlab::Ci::Config::External::Context)
.to receive(:check_execution_time!)
end
describe "#perform" do

View File

@ -7,6 +7,11 @@ describe Gitlab::Ci::Config do
set(:user) { create(:user) }
before do
allow_any_instance_of(Gitlab::Ci::Config::External::Context)
.to receive(:check_execution_time!)
end
let(:config) do
described_class.new(yml, project: nil, sha: nil, user: nil)
end
@ -303,6 +308,49 @@ describe Gitlab::Ci::Config do
end
end
context "when it takes too long to evaluate includes" do
before do
allow_any_instance_of(Gitlab::Ci::Config::External::Context)
.to receive(:check_execution_time!)
.and_call_original
allow_any_instance_of(Gitlab::Ci::Config::External::Context)
.to receive(:set_deadline)
.with(described_class::TIMEOUT_SECONDS)
.and_call_original
allow_any_instance_of(Gitlab::Ci::Config::External::Context)
.to receive(:execution_expired?)
.and_return(true)
end
it 'raises error TimeoutError' do
expect(Gitlab::Sentry).to receive(:track_exception)
expect { config }.to raise_error(
described_class::ConfigError,
'Resolving config took longer than expected'
)
end
end
context 'when context expansion timeout is disabled' do
before do
allow_any_instance_of(Gitlab::Ci::Config::External::Context)
.to receive(:check_execution_time!)
.and_call_original
allow(Feature)
.to receive(:enabled?)
.with(:ci_limit_yaml_expansion, project, default_enabled: true)
.and_return(false)
end
it 'does not raises errors' do
expect { config }.not_to raise_error
end
end
describe 'external file version' do
context 'when external local file SHA is defined' do
it 'is using a defined value' do

View File

@ -403,12 +403,30 @@ describe API::Internal::Base do
end
context 'when receive_max_input_size has been updated' do
it 'returns custom git config' do
before do
allow(Gitlab::CurrentSettings).to receive(:receive_max_input_size) { 1 }
end
it 'returns custom git config' do
push(key, project)
expect(json_response["git_config_options"]).to be_present
expect(json_response["git_config_options"]).to include("uploadpack.allowFilter=true")
expect(json_response["git_config_options"]).to include("uploadpack.allowAnySHA1InWant=true")
end
context 'when gitaly_upload_pack_filter feature flag is disabled' do
before do
stub_feature_flags(gitaly_upload_pack_filter: { enabled: false, thing: project })
end
it 'does not include allowFilter and allowAnySha1InWant in the git config options' do
push(key, project)
expect(json_response["git_config_options"]).to be_present
expect(json_response["git_config_options"]).not_to include("uploadpack.allowFilter=true")
expect(json_response["git_config_options"]).not_to include("uploadpack.allowAnySHA1InWant=true")
end
end
end