Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
f1a5755898
commit
2ed368929a
|
@ -1,5 +1,6 @@
|
||||||
extends:
|
extends:
|
||||||
- '@gitlab'
|
- '@gitlab'
|
||||||
|
- plugin:promise/recommended
|
||||||
globals:
|
globals:
|
||||||
__webpack_public_path__: true
|
__webpack_public_path__: true
|
||||||
gl: false
|
gl: false
|
||||||
|
@ -42,6 +43,11 @@ rules:
|
||||||
no-jquery/no-load: error
|
no-jquery/no-load: error
|
||||||
no-jquery/no-load-shorthand: error
|
no-jquery/no-load-shorthand: error
|
||||||
no-jquery/no-serialize: 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:
|
overrides:
|
||||||
files:
|
files:
|
||||||
- '**/spec/**/*'
|
- '**/spec/**/*'
|
||||||
|
|
|
@ -100,6 +100,7 @@
|
||||||
refs:
|
refs:
|
||||||
- master
|
- master
|
||||||
- /^\d+-\d+-auto-deploy-\d+$/
|
- /^\d+-\d+-auto-deploy-\d+$/
|
||||||
|
- /^[\d-]+-stable(-ee)?$/
|
||||||
|
|
||||||
.only-review-schedules:
|
.only-review-schedules:
|
||||||
only:
|
only:
|
||||||
|
|
|
@ -1,8 +1,15 @@
|
||||||
|
.except-deploys:
|
||||||
|
except:
|
||||||
|
refs:
|
||||||
|
- /^[\d-]+-stable(-ee)?$/
|
||||||
|
- /^\d+-\d+-auto-deploy-\d+$/
|
||||||
|
|
||||||
.review-docker:
|
.review-docker:
|
||||||
extends:
|
extends:
|
||||||
- .default-tags
|
- .default-tags
|
||||||
- .default-retry
|
- .default-retry
|
||||||
- .default-only
|
- .default-only
|
||||||
|
- .except-deploys
|
||||||
image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-qa-alpine
|
image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-qa-alpine
|
||||||
services:
|
services:
|
||||||
- docker:19.03.0-dind
|
- docker:19.03.0-dind
|
||||||
|
@ -36,6 +43,7 @@ schedule:review-cleanup:
|
||||||
- .default-only
|
- .default-only
|
||||||
- .only-code-qa-changes
|
- .only-code-qa-changes
|
||||||
- .only-review-schedules
|
- .only-review-schedules
|
||||||
|
- .except-deploys
|
||||||
stage: prepare
|
stage: prepare
|
||||||
image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-charts-build-base
|
image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-charts-build-base
|
||||||
allow_failure: true
|
allow_failure: true
|
||||||
|
@ -52,6 +60,7 @@ schedule:review-cleanup:
|
||||||
extends:
|
extends:
|
||||||
- .default-only
|
- .default-only
|
||||||
- .only-code-qa-changes
|
- .only-code-qa-changes
|
||||||
|
- .except-deploys
|
||||||
image: ruby:2.6-alpine
|
image: ruby:2.6-alpine
|
||||||
stage: review-prepare
|
stage: review-prepare
|
||||||
before_script:
|
before_script:
|
||||||
|
@ -80,6 +89,7 @@ schedule:review-build-cng:
|
||||||
- .default-retry
|
- .default-retry
|
||||||
- .default-only
|
- .default-only
|
||||||
- .only-code-qa-changes
|
- .only-code-qa-changes
|
||||||
|
- .except-deploys
|
||||||
stage: review
|
stage: review
|
||||||
image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-charts-build-base
|
image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-charts-build-base
|
||||||
dependencies: []
|
dependencies: []
|
||||||
|
@ -257,6 +267,7 @@ parallel-spec-reports:
|
||||||
- .default-only
|
- .default-only
|
||||||
- .only-code-qa-changes
|
- .only-code-qa-changes
|
||||||
- .only-review
|
- .only-review
|
||||||
|
- .except-deploys
|
||||||
image: ruby:2.6-alpine
|
image: ruby:2.6-alpine
|
||||||
stage: post-test
|
stage: post-test
|
||||||
dependencies: ["review-qa-all"]
|
dependencies: ["review-qa-all"]
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
|
import GlFeatureFlagsPlugin from '~/vue_shared/gl_feature_flags_plugin';
|
||||||
|
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
Vue.config.productionTip = false;
|
Vue.config.productionTip = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Vue.use(GlFeatureFlagsPlugin);
|
||||||
|
|
|
@ -175,7 +175,6 @@ export default {
|
||||||
'metricsWithData',
|
'metricsWithData',
|
||||||
'useDashboardEndpoint',
|
'useDashboardEndpoint',
|
||||||
'allDashboards',
|
'allDashboards',
|
||||||
'multipleDashboardsEnabled',
|
|
||||||
'additionalPanelTypesEnabled',
|
'additionalPanelTypesEnabled',
|
||||||
]),
|
]),
|
||||||
firstDashboard() {
|
firstDashboard() {
|
||||||
|
@ -318,7 +317,6 @@ export default {
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<template v-if="environmentsEndpoint">
|
<template v-if="environmentsEndpoint">
|
||||||
<gl-form-group
|
<gl-form-group
|
||||||
v-if="multipleDashboardsEnabled"
|
|
||||||
:label="__('Dashboard')"
|
:label="__('Dashboard')"
|
||||||
label-size="sm"
|
label-size="sm"
|
||||||
label-for="monitor-dashboards-dropdown"
|
label-for="monitor-dashboards-dropdown"
|
||||||
|
|
|
@ -14,7 +14,6 @@ export default (props = {}) => {
|
||||||
if (gon.features) {
|
if (gon.features) {
|
||||||
store.dispatch('monitoringDashboard/setFeatureFlags', {
|
store.dispatch('monitoringDashboard/setFeatureFlags', {
|
||||||
prometheusEndpointEnabled: gon.features.environmentMetricsUsePrometheusEndpoint,
|
prometheusEndpointEnabled: gon.features.environmentMetricsUsePrometheusEndpoint,
|
||||||
multipleDashboardsEnabled: gon.features.environmentMetricsShowMultipleDashboards,
|
|
||||||
additionalPanelTypesEnabled: gon.features.environmentMetricsAdditionalPanelTypes,
|
additionalPanelTypesEnabled: gon.features.environmentMetricsAdditionalPanelTypes,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,10 +37,9 @@ export const setEndpoints = ({ commit }, endpoints) => {
|
||||||
|
|
||||||
export const setFeatureFlags = (
|
export const setFeatureFlags = (
|
||||||
{ commit },
|
{ commit },
|
||||||
{ prometheusEndpointEnabled, multipleDashboardsEnabled, additionalPanelTypesEnabled },
|
{ prometheusEndpointEnabled, additionalPanelTypesEnabled },
|
||||||
) => {
|
) => {
|
||||||
commit(types.SET_DASHBOARD_ENABLED, prometheusEndpointEnabled);
|
commit(types.SET_DASHBOARD_ENABLED, prometheusEndpointEnabled);
|
||||||
commit(types.SET_MULTIPLE_DASHBOARDS_ENABLED, multipleDashboardsEnabled);
|
|
||||||
commit(types.SET_ADDITIONAL_PANEL_TYPES_ENABLED, additionalPanelTypesEnabled);
|
commit(types.SET_ADDITIONAL_PANEL_TYPES_ENABLED, additionalPanelTypesEnabled);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -51,13 +50,8 @@ export const setShowErrorBanner = ({ commit }, enabled) => {
|
||||||
export const requestMetricsDashboard = ({ commit }) => {
|
export const requestMetricsDashboard = ({ commit }) => {
|
||||||
commit(types.REQUEST_METRICS_DATA);
|
commit(types.REQUEST_METRICS_DATA);
|
||||||
};
|
};
|
||||||
export const receiveMetricsDashboardSuccess = (
|
export const receiveMetricsDashboardSuccess = ({ commit, dispatch }, { response, params }) => {
|
||||||
{ state, commit, dispatch },
|
commit(types.SET_ALL_DASHBOARDS, response.all_dashboards);
|
||||||
{ response, params },
|
|
||||||
) => {
|
|
||||||
if (state.multipleDashboardsEnabled) {
|
|
||||||
commit(types.SET_ALL_DASHBOARDS, response.all_dashboards);
|
|
||||||
}
|
|
||||||
commit(types.RECEIVE_METRICS_DATA_SUCCESS, response.dashboard.panel_groups);
|
commit(types.RECEIVE_METRICS_DATA_SUCCESS, response.dashboard.panel_groups);
|
||||||
dispatch('fetchPrometheusMetrics', params);
|
dispatch('fetchPrometheusMetrics', params);
|
||||||
};
|
};
|
||||||
|
|
|
@ -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_QUERY_RESULT = 'SET_QUERY_RESULT';
|
||||||
export const SET_TIME_WINDOW = 'SET_TIME_WINDOW';
|
export const SET_TIME_WINDOW = 'SET_TIME_WINDOW';
|
||||||
export const SET_DASHBOARD_ENABLED = 'SET_DASHBOARD_ENABLED';
|
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_ADDITIONAL_PANEL_TYPES_ENABLED = 'SET_ADDITIONAL_PANEL_TYPES_ENABLED';
|
||||||
export const SET_ALL_DASHBOARDS = 'SET_ALL_DASHBOARDS';
|
export const SET_ALL_DASHBOARDS = 'SET_ALL_DASHBOARDS';
|
||||||
export const SET_ENDPOINTS = 'SET_ENDPOINTS';
|
export const SET_ENDPOINTS = 'SET_ENDPOINTS';
|
||||||
|
|
|
@ -89,9 +89,6 @@ export default {
|
||||||
[types.SET_DASHBOARD_ENABLED](state, enabled) {
|
[types.SET_DASHBOARD_ENABLED](state, enabled) {
|
||||||
state.useDashboardEndpoint = enabled;
|
state.useDashboardEndpoint = enabled;
|
||||||
},
|
},
|
||||||
[types.SET_MULTIPLE_DASHBOARDS_ENABLED](state, enabled) {
|
|
||||||
state.multipleDashboardsEnabled = enabled;
|
|
||||||
},
|
|
||||||
[types.SET_GETTING_STARTED_EMPTY_STATE](state) {
|
[types.SET_GETTING_STARTED_EMPTY_STATE](state) {
|
||||||
state.emptyState = 'gettingStarted';
|
state.emptyState = 'gettingStarted';
|
||||||
},
|
},
|
||||||
|
|
|
@ -8,7 +8,6 @@ export default () => ({
|
||||||
deploymentsEndpoint: null,
|
deploymentsEndpoint: null,
|
||||||
dashboardEndpoint: invalidUrl,
|
dashboardEndpoint: invalidUrl,
|
||||||
useDashboardEndpoint: false,
|
useDashboardEndpoint: false,
|
||||||
multipleDashboardsEnabled: false,
|
|
||||||
additionalPanelTypesEnabled: false,
|
additionalPanelTypesEnabled: false,
|
||||||
emptyState: 'gettingStarted',
|
emptyState: 'gettingStarted',
|
||||||
showEmptyState: true,
|
showEmptyState: true,
|
||||||
|
|
|
@ -7,6 +7,7 @@ import _ from 'underscore';
|
||||||
import axios from './lib/utils/axios_utils';
|
import axios from './lib/utils/axios_utils';
|
||||||
import { s__, __, sprintf } from './locale';
|
import { s__, __, sprintf } from './locale';
|
||||||
import ModalStore from './boards/stores/modal_store';
|
import ModalStore from './boards/stores/modal_store';
|
||||||
|
import { parseBoolean } from './lib/utils/common_utils';
|
||||||
|
|
||||||
// TODO: remove eventHub hack after code splitting refactor
|
// TODO: remove eventHub hack after code splitting refactor
|
||||||
window.emitSidebarEvent = window.emitSidebarEvent || $.noop;
|
window.emitSidebarEvent = window.emitSidebarEvent || $.noop;
|
||||||
|
@ -275,12 +276,13 @@ function UsersSelect(currentUser, els, options = {}) {
|
||||||
})
|
})
|
||||||
.map(input => {
|
.map(input => {
|
||||||
const userId = parseInt(input.value, 10);
|
const userId = parseInt(input.value, 10);
|
||||||
const { avatarUrl, avatar_url, name, username } = input.dataset;
|
const { avatarUrl, avatar_url, name, username, canMerge } = input.dataset;
|
||||||
return {
|
return {
|
||||||
avatar_url: avatarUrl || avatar_url,
|
avatar_url: avatarUrl || avatar_url,
|
||||||
id: userId,
|
id: userId,
|
||||||
name,
|
name,
|
||||||
username,
|
username,
|
||||||
|
can_merge: parseBoolean(canMerge),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
|
import { __ } from '~/locale';
|
||||||
|
import createFlash from '~/flash';
|
||||||
import statusIcon from '../mr_widget_status_icon.vue';
|
import statusIcon from '../mr_widget_status_icon.vue';
|
||||||
import tooltip from '../../../vue_shared/directives/tooltip';
|
import tooltip from '../../../vue_shared/directives/tooltip';
|
||||||
import eventHub from '../../event_hub';
|
import eventHub from '../../event_hub';
|
||||||
|
@ -29,12 +31,12 @@ export default {
|
||||||
.then(res => res.data)
|
.then(res => res.data)
|
||||||
.then(data => {
|
.then(data => {
|
||||||
eventHub.$emit('UpdateWidgetData', 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);
|
$('.merge-request .detail-page-description .title').text(this.mr.title);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
this.isMakingRequest = false;
|
this.isMakingRequest = false;
|
||||||
new window.Flash('Something went wrong. Please try again.'); // eslint-disable-line
|
createFlash(__('Something went wrong. Please try again.'));
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
export default Vue => {
|
||||||
|
Vue.mixin({
|
||||||
|
provide: {
|
||||||
|
glFeatures: { ...((window.gon && window.gon.features) || {}) },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
|
@ -0,0 +1,8 @@
|
||||||
|
export default () => ({
|
||||||
|
inject: {
|
||||||
|
glFeatures: {
|
||||||
|
from: 'glFeatures',
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
|
@ -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 {
|
.note-emoji-button {
|
||||||
position: relative;
|
position: relative;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
|
@ -635,10 +627,6 @@ $note-form-margin-left: 72px;
|
||||||
|
|
||||||
.note-headline-light {
|
.note-headline-light {
|
||||||
display: inline;
|
display: inline;
|
||||||
|
|
||||||
@include notes-media('max', map-get($grid-breakpoints, xs)) {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.note-headline-light,
|
.note-headline-light,
|
||||||
|
|
|
@ -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
|
|
@ -12,7 +12,6 @@ class Projects::EnvironmentsController < Projects::ApplicationController
|
||||||
before_action :expire_etag_cache, only: [:index]
|
before_action :expire_etag_cache, only: [:index]
|
||||||
before_action only: [:metrics, :additional_metrics, :metrics_dashboard] do
|
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_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(:environment_metrics_additional_panel_types)
|
||||||
push_frontend_feature_flag(:prometheus_computed_alerts)
|
push_frontend_feature_flag(:prometheus_computed_alerts)
|
||||||
end
|
end
|
||||||
|
@ -168,7 +167,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController
|
||||||
dashboard_path: params[:dashboard],
|
dashboard_path: params[:dashboard],
|
||||||
**dashboard_params.to_h.symbolize_keys
|
**dashboard_params.to_h.symbolize_keys
|
||||||
)
|
)
|
||||||
elsif Feature.enabled?(:environment_metrics_show_multiple_dashboards, project)
|
else
|
||||||
result = dashboard_finder.find(
|
result = dashboard_finder.find(
|
||||||
project,
|
project,
|
||||||
current_user,
|
current_user,
|
||||||
|
@ -177,8 +176,6 @@ class Projects::EnvironmentsController < Projects::ApplicationController
|
||||||
)
|
)
|
||||||
|
|
||||||
result[:all_dashboards] = dashboard_finder.find_all_paths(project)
|
result[:all_dashboards] = dashboard_finder.find_all_paths(project)
|
||||||
else
|
|
||||||
result = dashboard_finder.find(project, current_user, environment: environment)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
|
|
|
@ -5,6 +5,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
|
||||||
include IssuableActions
|
include IssuableActions
|
||||||
include RendersNotes
|
include RendersNotes
|
||||||
include RendersCommits
|
include RendersCommits
|
||||||
|
include RendersAssignees
|
||||||
include ToggleAwardEmoji
|
include ToggleAwardEmoji
|
||||||
include IssuableCollections
|
include IssuableCollections
|
||||||
include RecordUserLastActivity
|
include RecordUserLastActivity
|
||||||
|
@ -41,6 +42,8 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
|
||||||
# use next to appease Rubocop
|
# use next to appease Rubocop
|
||||||
next render('invalid') if target_branch_missing?
|
next render('invalid') if target_branch_missing?
|
||||||
|
|
||||||
|
preload_assignees_for_render(@merge_request)
|
||||||
|
|
||||||
# Build a note object for comment form
|
# Build a note object for comment form
|
||||||
@note = @project.notes.new(noteable: @merge_request)
|
@note = @project.notes.new(noteable: @merge_request)
|
||||||
|
|
||||||
|
|
|
@ -372,6 +372,12 @@ module IssuablesHelper
|
||||||
finder.class.scalar_params.any? { |p| params[p].present? }
|
finder.class.scalar_params.any? { |p| params[p].present? }
|
||||||
end
|
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
|
private
|
||||||
|
|
||||||
def sidebar_gutter_collapsed?
|
def sidebar_gutter_collapsed?
|
||||||
|
|
|
@ -40,7 +40,7 @@
|
||||||
.info-well
|
.info-well
|
||||||
.well-segment.admin-well.admin-well-features
|
.well-segment.admin-well.admin-well-features
|
||||||
%h4 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(_('LDAP'), enabled: Gitlab.config.ldap.enabled)
|
||||||
= feature_entry(_('Gravatar'), href: admin_application_settings_path(anchor: 'js-account-settings'), enabled: gravatar_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?)
|
= feature_entry(_('OmniAuth'), href: admin_application_settings_path(anchor: 'js-signin-settings'), enabled: Gitlab::Auth.omniauth_enabled?)
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
= hidden_field_tag "#{issuable_type}[assignee_ids][]", 0, id: nil
|
= hidden_field_tag "#{issuable_type}[assignee_ids][]", 0, id: nil
|
||||||
- else
|
- else
|
||||||
- assignees.each do |assignee|
|
- 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',
|
- options = { toggle_class: 'js-user-search js-author-search',
|
||||||
title: _('Assign to'),
|
title: _('Assign to'),
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Remove map-get($grid-breakpoints, xs) for max-width
|
||||||
|
merge_request: 17420
|
||||||
|
author: Takuya Noguchi
|
||||||
|
type: other
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Fix cannot merge icon showing in dropdown for users who can merge
|
||||||
|
merge_request: 17306
|
||||||
|
author:
|
||||||
|
type: fixed
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Fix signup link in admin area not being disabled
|
||||||
|
merge_request: 16726
|
||||||
|
author: Illya Klymov
|
||||||
|
type: fixed
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Add allowFilter and allowAnySHA1InWant for partial clones
|
||||||
|
merge_request: 16850
|
||||||
|
author:
|
||||||
|
type: added
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Add timeout mechanism for CI config validation
|
||||||
|
merge_request: 16807
|
||||||
|
author:
|
||||||
|
type: fixed
|
|
@ -2289,6 +2289,10 @@ Nested includes allow you to compose a set of includes.
|
||||||
A total of 50 includes is allowed.
|
A total of 50 includes is allowed.
|
||||||
Duplicate includes are considered a configuration error.
|
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
|
#### `include` examples
|
||||||
|
|
||||||
Here are a few more `include` examples.
|
Here are a few more `include` examples.
|
||||||
|
|
|
@ -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
|
### A folder for Components
|
||||||
|
|
||||||
This folder holds all components that are specific of this new feature.
|
This folder holds all components that are specific of this new feature.
|
||||||
|
|
|
@ -109,6 +109,9 @@ if ( gon.features.vimBindings ) {
|
||||||
The name of the feature flag in JavaScript will always be camelCased, meaning
|
The name of the feature flag in JavaScript will always be camelCased, meaning
|
||||||
that checking for `gon.features.vim_bindings` would not work.
|
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
|
### Specs
|
||||||
|
|
||||||
In the test environment `Feature.enabled?` is stubbed to always respond to `true`,
|
In the test environment `Feature.enabled?` is stubbed to always respond to `true`,
|
||||||
|
|
|
@ -78,6 +78,10 @@ module API
|
||||||
receive_max_input_size = Gitlab::CurrentSettings.receive_max_input_size.to_i
|
receive_max_input_size = Gitlab::CurrentSettings.receive_max_input_size.to_i
|
||||||
if receive_max_input_size > 0
|
if receive_max_input_size > 0
|
||||||
payload[:git_config_options] << "receive.maxInputSize=#{receive_max_input_size.megabytes}"
|
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
|
end
|
||||||
|
|
||||||
response_with_status(**payload)
|
response_with_status(**payload)
|
||||||
|
|
|
@ -7,6 +7,8 @@ module Gitlab
|
||||||
#
|
#
|
||||||
class Config
|
class Config
|
||||||
ConfigError = Class.new(StandardError)
|
ConfigError = Class.new(StandardError)
|
||||||
|
TIMEOUT_SECONDS = 30.seconds
|
||||||
|
TIMEOUT_MESSAGE = 'Resolving config took longer than expected'
|
||||||
|
|
||||||
RESCUE_ERRORS = [
|
RESCUE_ERRORS = [
|
||||||
Gitlab::Config::Loader::FormatError,
|
Gitlab::Config::Loader::FormatError,
|
||||||
|
@ -17,17 +19,17 @@ module Gitlab
|
||||||
attr_reader :root
|
attr_reader :root
|
||||||
|
|
||||||
def initialize(config, project: nil, sha: nil, user: nil)
|
def initialize(config, project: nil, sha: nil, user: nil)
|
||||||
@config = Config::Extendable
|
@context = build_context(project: project, sha: sha, user: user)
|
||||||
.new(build_config(config, project: project, sha: sha, user: user))
|
|
||||||
.to_hash
|
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 = Entry::Root.new(@config)
|
||||||
@root.compose!
|
@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
|
rescue *rescue_errors => e
|
||||||
raise Config::ConfigError, e.message
|
raise Config::ConfigError, e.message
|
||||||
end
|
end
|
||||||
|
@ -61,18 +63,34 @@ module Gitlab
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def build_config(config, project:, sha:, user:)
|
def expand_config(config)
|
||||||
initial_config = Gitlab::Config::Loader::Yaml.new(config).load!
|
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
|
end
|
||||||
|
|
||||||
def process_external_files(config, project:, sha:, user:)
|
def build_config(config)
|
||||||
Config::External::Processor.new(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,
|
project: project,
|
||||||
sha: sha || project&.repository&.root_ref_sha,
|
sha: sha || project&.repository&.root_ref_sha,
|
||||||
user: user,
|
user: user)
|
||||||
expandset: Set.new).perform
|
end
|
||||||
|
|
||||||
|
def track_exception(error)
|
||||||
|
Gitlab::Sentry.track_exception(error, extra: @context.sentry_payload)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Overriden in EE
|
# Overriden in EE
|
||||||
|
|
|
@ -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
|
|
@ -12,8 +12,6 @@ module Gitlab
|
||||||
|
|
||||||
YAML_WHITELIST_EXTENSION = /.+\.(yml|yaml)$/i.freeze
|
YAML_WHITELIST_EXTENSION = /.+\.(yml|yaml)$/i.freeze
|
||||||
|
|
||||||
Context = Struct.new(:project, :sha, :user, :expandset)
|
|
||||||
|
|
||||||
def initialize(params, context)
|
def initialize(params, context)
|
||||||
@params = params
|
@params = params
|
||||||
@context = context
|
@context = context
|
||||||
|
@ -69,11 +67,16 @@ module Gitlab
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate!
|
def validate!
|
||||||
|
validate_execution_time!
|
||||||
validate_location!
|
validate_location!
|
||||||
validate_content! if errors.none?
|
validate_content! if errors.none?
|
||||||
validate_hash! if errors.none?
|
validate_hash! if errors.none?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def validate_execution_time!
|
||||||
|
context.check_execution_time!
|
||||||
|
end
|
||||||
|
|
||||||
def validate_location!
|
def validate_location!
|
||||||
if invalid_location_type?
|
if invalid_location_type?
|
||||||
errors.push("Included file `#{location}` needs to be a string")
|
errors.push("Included file `#{location}` needs to be a string")
|
||||||
|
@ -95,11 +98,11 @@ module Gitlab
|
||||||
end
|
end
|
||||||
|
|
||||||
def expand_includes(hash)
|
def expand_includes(hash)
|
||||||
External::Processor.new(hash, **expand_context).perform
|
External::Processor.new(hash, context.mutate(expand_context_attrs)).perform
|
||||||
end
|
end
|
||||||
|
|
||||||
def expand_context
|
def expand_context_attrs
|
||||||
{ project: nil, sha: nil, user: nil, expandset: context.expandset }
|
{}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,6 +6,7 @@ module Gitlab
|
||||||
module External
|
module External
|
||||||
module File
|
module File
|
||||||
class Local < Base
|
class Local < Base
|
||||||
|
extend ::Gitlab::Utils::Override
|
||||||
include Gitlab::Utils::StrongMemoize
|
include Gitlab::Utils::StrongMemoize
|
||||||
|
|
||||||
def initialize(params, context)
|
def initialize(params, context)
|
||||||
|
@ -34,11 +35,13 @@ module Gitlab
|
||||||
context.project.repository.blob_data_at(context.sha, location)
|
context.project.repository.blob_data_at(context.sha, location)
|
||||||
end
|
end
|
||||||
|
|
||||||
def expand_context
|
override :expand_context_attrs
|
||||||
super.merge(
|
def expand_context_attrs
|
||||||
|
{
|
||||||
project: context.project,
|
project: context.project,
|
||||||
sha: context.sha,
|
sha: context.sha,
|
||||||
user: context.user)
|
user: context.user
|
||||||
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,11 +6,12 @@ module Gitlab
|
||||||
module External
|
module External
|
||||||
module File
|
module File
|
||||||
class Project < Base
|
class Project < Base
|
||||||
|
extend ::Gitlab::Utils::Override
|
||||||
include Gitlab::Utils::StrongMemoize
|
include Gitlab::Utils::StrongMemoize
|
||||||
|
|
||||||
attr_reader :project_name, :ref_name
|
attr_reader :project_name, :ref_name
|
||||||
|
|
||||||
def initialize(params, context = {})
|
def initialize(params, context)
|
||||||
@location = params[:file]
|
@location = params[:file]
|
||||||
@project_name = params[:project]
|
@project_name = params[:project]
|
||||||
@ref_name = params[:ref] || 'HEAD'
|
@ref_name = params[:ref] || 'HEAD'
|
||||||
|
@ -65,11 +66,13 @@ module Gitlab
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def expand_context
|
override :expand_context_attrs
|
||||||
super.merge(
|
def expand_context_attrs
|
||||||
|
{
|
||||||
project: project,
|
project: project,
|
||||||
sha: sha,
|
sha: sha,
|
||||||
user: context.user)
|
user: context.user
|
||||||
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -21,14 +21,9 @@ module Gitlab
|
||||||
DuplicateIncludesError = Class.new(Error)
|
DuplicateIncludesError = Class.new(Error)
|
||||||
TooManyIncludesError = Class.new(Error)
|
TooManyIncludesError = Class.new(Error)
|
||||||
|
|
||||||
def initialize(values, project:, sha:, user:, expandset:)
|
def initialize(values, context)
|
||||||
raise Error, 'Expanded needs to be `Set`' unless expandset.is_a?(Set)
|
|
||||||
|
|
||||||
@locations = Array.wrap(values.fetch(:include, []))
|
@locations = Array.wrap(values.fetch(:include, []))
|
||||||
@project = project
|
@context = context
|
||||||
@sha = sha
|
|
||||||
@user = user
|
|
||||||
@expandset = expandset
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def process
|
def process
|
||||||
|
@ -43,7 +38,9 @@ module Gitlab
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
attr_reader :locations, :project, :sha, :user, :expandset
|
attr_reader :locations, :context
|
||||||
|
|
||||||
|
delegate :expandset, to: :context
|
||||||
|
|
||||||
# convert location if String to canonical form
|
# convert location if String to canonical form
|
||||||
def normalize_location(location)
|
def normalize_location(location)
|
||||||
|
@ -68,11 +65,11 @@ module Gitlab
|
||||||
end
|
end
|
||||||
|
|
||||||
# We scope location to context, as this allows us to properly support
|
# 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
|
# does not trigger duplicate error
|
||||||
scoped_location = location.merge(
|
scoped_location = location.merge(
|
||||||
context_project: project,
|
context_project: context.project,
|
||||||
context_sha: sha)
|
context_sha: context.sha)
|
||||||
|
|
||||||
unless expandset.add?(scoped_location)
|
unless expandset.add?(scoped_location)
|
||||||
raise DuplicateIncludesError, "Include `#{location.to_json}` was already included!"
|
raise DuplicateIncludesError, "Include `#{location.to_json}` was already included!"
|
||||||
|
@ -88,12 +85,6 @@ module Gitlab
|
||||||
|
|
||||||
matching.first
|
matching.first
|
||||||
end
|
end
|
||||||
|
|
||||||
def context
|
|
||||||
strong_memoize(:context) do
|
|
||||||
External::File::Base::Context.new(project, sha, user, expandset)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,9 +7,9 @@ module Gitlab
|
||||||
class Processor
|
class Processor
|
||||||
IncludeError = Class.new(StandardError)
|
IncludeError = Class.new(StandardError)
|
||||||
|
|
||||||
def initialize(values, project:, sha:, user:, expandset:)
|
def initialize(values, context)
|
||||||
@values = values
|
@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 = {}
|
@content = {}
|
||||||
rescue External::Mapper::Error,
|
rescue External::Mapper::Error,
|
||||||
OpenSSL::SSL::SSLError => e
|
OpenSSL::SSL::SSLError => e
|
||||||
|
|
|
@ -15527,6 +15527,9 @@ msgstr ""
|
||||||
msgid "The merge conflicts for this merge request have already been resolved. Please return to the merge request."
|
msgid "The merge conflicts for this merge request have already been resolved. Please return to the merge request."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "The merge request can now be merged."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "The name %{entryName} is already taken in this directory."
|
msgid "The name %{entryName} is already taken in this directory."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
|
@ -483,10 +483,8 @@ describe Projects::EnvironmentsController do
|
||||||
end
|
end
|
||||||
|
|
||||||
shared_examples_for 'the default dashboard' do
|
shared_examples_for 'the default dashboard' do
|
||||||
all_dashboards = Feature.enabled?(:environment_metrics_show_multiple_dashboards)
|
|
||||||
|
|
||||||
it_behaves_like '200 response'
|
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
|
it 'is the default dashboard' do
|
||||||
get :metrics_dashboard, params: environment_params(dashboard_params)
|
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 'the default dashboard'
|
||||||
it_behaves_like 'dashboard can be specified'
|
it_behaves_like 'dashboard can be specified'
|
||||||
it_behaves_like 'dashboard can be embedded'
|
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
|
end
|
||||||
|
|
||||||
describe 'GET #search' do
|
describe 'GET #search' do
|
||||||
|
|
|
@ -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
|
|
@ -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,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -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,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -243,4 +243,32 @@ describe IssuablesHelper do
|
||||||
end
|
end
|
||||||
end
|
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
|
end
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { createStore } from '~/create_cluster/gke_cluster/store';
|
||||||
import { SET_PROJECTS } from '~/create_cluster/gke_cluster/store/mutation_types';
|
import { SET_PROJECTS } from '~/create_cluster/gke_cluster/store/mutation_types';
|
||||||
import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
|
import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
|
||||||
import { emptyProjectMock, selectedProjectMock } from '../mock_data';
|
import { emptyProjectMock, selectedProjectMock } from '../mock_data';
|
||||||
|
import { gapi } from '../helpers';
|
||||||
|
|
||||||
const componentConfig = {
|
const componentConfig = {
|
||||||
docsUrl: 'https://console.cloud.google.com/home/dashboard',
|
docsUrl: 'https://console.cloud.google.com/home/dashboard',
|
||||||
|
@ -32,6 +33,16 @@ describe('GkeProjectIdDropdown', () => {
|
||||||
let vm;
|
let vm;
|
||||||
let store;
|
let store;
|
||||||
|
|
||||||
|
let originalGapi;
|
||||||
|
beforeAll(() => {
|
||||||
|
originalGapi = window.gapi;
|
||||||
|
window.gapi = gapi();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
window.gapi = originalGapi;
|
||||||
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
store = createStore();
|
store = createStore();
|
||||||
vm = createComponent(store);
|
vm = createComponent(store);
|
||||||
|
|
|
@ -64,7 +64,15 @@ describe('GCP Cluster Dropdown Store Actions', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('async fetch methods', () => {
|
describe('async fetch methods', () => {
|
||||||
window.gapi = gapi();
|
let originalGapi;
|
||||||
|
beforeAll(() => {
|
||||||
|
originalGapi = window.gapi;
|
||||||
|
window.gapi = gapi();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
window.gapi = originalGapi;
|
||||||
|
});
|
||||||
|
|
||||||
describe('fetchProjects', () => {
|
describe('fetchProjects', () => {
|
||||||
it('fetches projects from Google API', done => {
|
it('fetches projects from Google API', done => {
|
||||||
|
|
|
@ -527,7 +527,6 @@ describe('Dashboard', () => {
|
||||||
|
|
||||||
component.$store.dispatch('monitoringDashboard/setFeatureFlags', {
|
component.$store.dispatch('monitoringDashboard/setFeatureFlags', {
|
||||||
prometheusEndpoint: false,
|
prometheusEndpoint: false,
|
||||||
multipleDashboardsEnabled: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
component.$store.commit(
|
component.$store.commit(
|
||||||
|
|
|
@ -240,8 +240,6 @@ describe('Monitoring store actions', () => {
|
||||||
const response = metricsDashboardResponse;
|
const response = metricsDashboardResponse;
|
||||||
|
|
||||||
response.all_dashboards = dashboardGitResponse;
|
response.all_dashboards = dashboardGitResponse;
|
||||||
state.multipleDashboardsEnabled = true;
|
|
||||||
|
|
||||||
receiveMetricsDashboardSuccess({ state, commit, dispatch }, { response, params });
|
receiveMetricsDashboardSuccess({ state, commit, dispatch }, { response, params });
|
||||||
|
|
||||||
expect(commit).toHaveBeenCalledWith(types.SET_ALL_DASHBOARDS, dashboardGitResponse);
|
expect(commit).toHaveBeenCalledWith(types.SET_ALL_DASHBOARDS, dashboardGitResponse);
|
||||||
|
|
|
@ -47,7 +47,7 @@ describe('Wip', () => {
|
||||||
it('should make a request to service and handle response', done => {
|
it('should make a request to service and handle response', done => {
|
||||||
const vm = createComponent();
|
const vm = createComponent();
|
||||||
|
|
||||||
spyOn(window, 'Flash').and.returnValue(true);
|
const flashSpy = spyOnDependency(WorkInProgress, 'createFlash').and.returnValue(true);
|
||||||
spyOn(eventHub, '$emit');
|
spyOn(eventHub, '$emit');
|
||||||
spyOn(vm.service, 'removeWIP').and.returnValue(
|
spyOn(vm.service, 'removeWIP').and.returnValue(
|
||||||
new Promise(resolve => {
|
new Promise(resolve => {
|
||||||
|
@ -61,10 +61,7 @@ describe('Wip', () => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
expect(vm.isMakingRequest).toBeTruthy();
|
expect(vm.isMakingRequest).toBeTruthy();
|
||||||
expect(eventHub.$emit).toHaveBeenCalledWith('UpdateWidgetData', mrObj);
|
expect(eventHub.$emit).toHaveBeenCalledWith('UpdateWidgetData', mrObj);
|
||||||
expect(window.Flash).toHaveBeenCalledWith(
|
expect(flashSpy).toHaveBeenCalledWith('The merge request can now be merged.', 'notice');
|
||||||
'The merge request can now be merged.',
|
|
||||||
'notice',
|
|
||||||
);
|
|
||||||
done();
|
done();
|
||||||
}, 333);
|
}, 333);
|
||||||
});
|
});
|
||||||
|
|
|
@ -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
|
|
@ -1,13 +1,14 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require 'fast_spec_helper'
|
require 'spec_helper'
|
||||||
|
|
||||||
describe Gitlab::Ci::Config::External::File::Base do
|
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
|
let(:test_class) do
|
||||||
Class.new(described_class) do
|
Class.new(described_class) do
|
||||||
def initialize(params, context = {})
|
def initialize(params, context)
|
||||||
@location = params
|
@location = params
|
||||||
|
|
||||||
super
|
super
|
||||||
|
@ -20,6 +21,9 @@ describe Gitlab::Ci::Config::External::File::Base do
|
||||||
before do
|
before do
|
||||||
allow_any_instance_of(test_class)
|
allow_any_instance_of(test_class)
|
||||||
.to receive(:content).and_return('key: value')
|
.to receive(:content).and_return('key: value')
|
||||||
|
|
||||||
|
allow_any_instance_of(Gitlab::Ci::Config::External::Context)
|
||||||
|
.to receive(:check_execution_time!)
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#matching?' do
|
describe '#matching?' do
|
||||||
|
|
|
@ -7,10 +7,17 @@ describe Gitlab::Ci::Config::External::File::Local do
|
||||||
set(:user) { create(:user) }
|
set(:user) { create(:user) }
|
||||||
|
|
||||||
let(:sha) { '12345' }
|
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(:params) { { local: location } }
|
||||||
let(:local_file) { described_class.new(params, context) }
|
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
|
describe '#matching?' do
|
||||||
context 'when a local is specified' do
|
context 'when a local is specified' do
|
||||||
let(:params) { { local: 'file' } }
|
let(:params) { { local: 'file' } }
|
||||||
|
@ -109,7 +116,7 @@ describe Gitlab::Ci::Config::External::File::Local do
|
||||||
describe '#expand_context' do
|
describe '#expand_context' do
|
||||||
let(:location) { 'location.yml' }
|
let(:location) { 'location.yml' }
|
||||||
|
|
||||||
subject { local_file.send(:expand_context) }
|
subject { local_file.send(:expand_context_attrs) }
|
||||||
|
|
||||||
it 'inherits project, user and sha' do
|
it 'inherits project, user and sha' do
|
||||||
is_expected.to include(user: user, project: project, sha: sha)
|
is_expected.to include(user: user, project: project, sha: sha)
|
||||||
|
|
|
@ -8,11 +8,15 @@ describe Gitlab::Ci::Config::External::File::Project do
|
||||||
set(:user) { create(:user) }
|
set(:user) { create(:user) }
|
||||||
|
|
||||||
let(:context_user) { 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) }
|
let(:project_file) { described_class.new(params, context) }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
project.add_developer(user)
|
project.add_developer(user)
|
||||||
|
|
||||||
|
allow_any_instance_of(Gitlab::Ci::Config::External::Context)
|
||||||
|
.to receive(:check_execution_time!)
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#matching?' do
|
describe '#matching?' do
|
||||||
|
@ -145,7 +149,7 @@ describe Gitlab::Ci::Config::External::File::Project do
|
||||||
describe '#expand_context' do
|
describe '#expand_context' do
|
||||||
let(:params) { { file: 'file.yml', project: project.full_path, ref: 'master' } }
|
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
|
it 'inherits user, and target project and sha' do
|
||||||
is_expected.to include(user: user, project: project, sha: project.commit('master').id)
|
is_expected.to include(user: user, project: project, sha: project.commit('master').id)
|
||||||
|
|
|
@ -5,7 +5,8 @@ require 'spec_helper'
|
||||||
describe Gitlab::Ci::Config::External::File::Remote do
|
describe Gitlab::Ci::Config::External::File::Remote do
|
||||||
include StubRequests
|
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(:params) { { remote: location } }
|
||||||
let(:remote_file) { described_class.new(params, context) }
|
let(:remote_file) { described_class.new(params, context) }
|
||||||
let(:location) { 'https://gitlab.com/gitlab-org/gitlab-foss/blob/1234/.gitlab-ci-1.yml' }
|
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
|
HEREDOC
|
||||||
end
|
end
|
||||||
|
|
||||||
|
before do
|
||||||
|
allow_any_instance_of(Gitlab::Ci::Config::External::Context)
|
||||||
|
.to receive(:check_execution_time!)
|
||||||
|
end
|
||||||
|
|
||||||
describe '#matching?' do
|
describe '#matching?' do
|
||||||
context 'when a remote is specified' do
|
context 'when a remote is specified' do
|
||||||
let(:params) { { remote: 'http://remote' } }
|
let(:params) { { remote: 'http://remote' } }
|
||||||
|
@ -187,10 +193,10 @@ describe Gitlab::Ci::Config::External::File::Remote do
|
||||||
describe '#expand_context' do
|
describe '#expand_context' do
|
||||||
let(:params) { { remote: 'http://remote' } }
|
let(:params) { { remote: 'http://remote' } }
|
||||||
|
|
||||||
subject { remote_file.send(:expand_context) }
|
subject { remote_file.send(:expand_context_attrs) }
|
||||||
|
|
||||||
it 'drops all parameters' do
|
it 'drops all parameters' do
|
||||||
is_expected.to include(user: nil, project: nil, sha: nil)
|
is_expected.to be_empty
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,12 +6,18 @@ describe Gitlab::Ci::Config::External::File::Template do
|
||||||
set(:project) { create(:project) }
|
set(:project) { create(:project) }
|
||||||
set(:user) { create(:user) }
|
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(:template) { 'Auto-DevOps.gitlab-ci.yml' }
|
||||||
let(:params) { { template: template } }
|
let(:params) { { template: template } }
|
||||||
|
|
||||||
let(:template_file) { described_class.new(params, context) }
|
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
|
describe '#matching?' do
|
||||||
context 'when a template is specified' do
|
context 'when a template is specified' do
|
||||||
let(:params) { { template: 'some-template' } }
|
let(:params) { { template: 'some-template' } }
|
||||||
|
@ -97,10 +103,10 @@ describe Gitlab::Ci::Config::External::File::Template do
|
||||||
describe '#expand_context' do
|
describe '#expand_context' do
|
||||||
let(:location) { 'location.yml' }
|
let(:location) { 'location.yml' }
|
||||||
|
|
||||||
subject { template_file.send(:expand_context) }
|
subject { template_file.send(:expand_context_attrs) }
|
||||||
|
|
||||||
it 'drops all parameters' do
|
it 'drops all parameters' do
|
||||||
is_expected.to include(user: nil, project: nil, sha: nil)
|
is_expected.to be_empty
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -11,7 +11,8 @@ describe Gitlab::Ci::Config::External::Mapper do
|
||||||
let(:local_file) { '/lib/gitlab/ci/templates/non-existent-file.yml' }
|
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(:remote_url) { 'https://gitlab.com/gitlab-org/gitlab-foss/blob/1234/.gitlab-ci-1.yml' }
|
||||||
let(:template_file) { 'Auto-DevOps.gitlab-ci.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
|
let(:file_content) do
|
||||||
<<~HEREDOC
|
<<~HEREDOC
|
||||||
|
@ -21,10 +22,13 @@ describe Gitlab::Ci::Config::External::Mapper do
|
||||||
|
|
||||||
before do
|
before do
|
||||||
stub_full_request(remote_url).to_return(body: file_content)
|
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
|
end
|
||||||
|
|
||||||
describe '#process' do
|
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 single 'include' keyword is defined" do
|
||||||
context 'when the string is a local file' do
|
context 'when the string is a local file' do
|
||||||
|
|
|
@ -9,12 +9,16 @@ describe Gitlab::Ci::Config::External::Processor do
|
||||||
set(:another_project) { create(:project, :repository) }
|
set(:another_project) { create(:project, :repository) }
|
||||||
set(:user) { create(:user) }
|
set(:user) { create(:user) }
|
||||||
|
|
||||||
let(:expandset) { Set.new }
|
|
||||||
let(:sha) { '12345' }
|
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
|
before do
|
||||||
project.add_developer(user)
|
project.add_developer(user)
|
||||||
|
|
||||||
|
allow_any_instance_of(Gitlab::Ci::Config::External::Context)
|
||||||
|
.to receive(:check_execution_time!)
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "#perform" do
|
describe "#perform" do
|
||||||
|
|
|
@ -7,6 +7,11 @@ describe Gitlab::Ci::Config do
|
||||||
|
|
||||||
set(:user) { create(:user) }
|
set(:user) { create(:user) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
allow_any_instance_of(Gitlab::Ci::Config::External::Context)
|
||||||
|
.to receive(:check_execution_time!)
|
||||||
|
end
|
||||||
|
|
||||||
let(:config) do
|
let(:config) do
|
||||||
described_class.new(yml, project: nil, sha: nil, user: nil)
|
described_class.new(yml, project: nil, sha: nil, user: nil)
|
||||||
end
|
end
|
||||||
|
@ -303,6 +308,49 @@ describe Gitlab::Ci::Config do
|
||||||
end
|
end
|
||||||
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
|
describe 'external file version' do
|
||||||
context 'when external local file SHA is defined' do
|
context 'when external local file SHA is defined' do
|
||||||
it 'is using a defined value' do
|
it 'is using a defined value' do
|
||||||
|
|
|
@ -403,12 +403,30 @@ describe API::Internal::Base do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when receive_max_input_size has been updated' do
|
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 }
|
allow(Gitlab::CurrentSettings).to receive(:receive_max_input_size) { 1 }
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns custom git config' do
|
||||||
push(key, project)
|
push(key, project)
|
||||||
|
|
||||||
expect(json_response["git_config_options"]).to be_present
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue