Commit 58423fa8 authored by Douglas Barbosa Alexandre's avatar Douglas Barbosa Alexandre

Merge remote-tracking branch 'origin/master' into...

Merge remote-tracking branch 'origin/master' into camilstaps/gitlab-ce-new-66023-public-private-fork-counts
parents ed1192c5 814d12b8
......@@ -65,6 +65,7 @@ eslint-report.html
/vendor/gitaly-ruby
/builds*
/.gitlab_workhorse_secret
/.gitlab_pages_shared_secret
/webpack-report/
/knapsack/
/rspec_flaky/
......
......@@ -28,7 +28,8 @@ package-and-qa-manual:master:
extends: .package-and-qa-base
only:
refs:
- master
- master@gitlab-org/gitlab-ce
- master@gitlab-org/gitlab-ee
when: manual
needs: ["build-qa-image", "gitlab:assets:compile"]
......
......@@ -5,7 +5,17 @@
### Intended users
<!-- Who will use this feature? If known, include any of the following: types of users (e.g. Developer), personas, or specific company roles (e.g. Release Manager). It's okay to write "Unknown" and fill this field in later.
Personas can be found at https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas/ -->
* [Parker (Product Manager)](https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas/#parker-product-manager)
* [Delaney (Development Team Lead)](https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas/#delaney-development-team-lead)
* [Sasha (Software Developer)](https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas/#sasha-software-developer)
* [Presley (Product Designer)](https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas/#presley-product-designer)
* [Devon (DevOps Engineer)](https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas/#devon-devops-engineer)
* [Sidney (Systems Administrator)](https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas/#sidney-systems-administrator)
* [Sam (Security Analyst)](https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas/#sam-security-analyst)
* [Dana (Data Analyst)](https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas/#dana-data-analyst)
Personas are described at https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas/ -->
### Further details
......
......@@ -15,6 +15,7 @@
## Author's checklist
- [ ] Follow the [Documentation Guidelines](https://docs.gitlab.com/ee/development/documentation/) and [Style Guide](https://docs.gitlab.com/ee/development/documentation/styleguide.html).
- [ ] If applicable, update the [permissions table](https://docs.gitlab.com/ee/user/permissions.html).
- [ ] Link docs to and from the higher-level index page, plus other related docs where helpful.
- [ ] Apply the ~Documentation label.
......
......@@ -106,7 +106,7 @@ gem 'fog-aws', '~> 3.5'
# Locked until fog-google resolves https://github.com/fog/fog-google/issues/421.
# Also see config/initializers/fog_core_patch.rb.
gem 'fog-core', '= 2.1.0'
gem 'fog-google', '~> 1.8'
gem 'fog-google', '~> 1.9'
gem 'fog-local', '~> 0.6'
gem 'fog-openstack', '~> 1.0'
gem 'fog-rackspace', '~> 0.1.1'
......@@ -135,7 +135,7 @@ gem 'wikicloth', '0.8.1'
gem 'asciidoctor', '~> 2.0.10'
gem 'asciidoctor-include-ext', '~> 0.3.1', require: false
gem 'asciidoctor-plantuml', '0.0.9'
gem 'rouge', '~> 3.7'
gem 'rouge', '~> 3.10'
gem 'truncato', '~> 0.7.11'
gem 'bootstrap_form', '~> 4.2.0'
gem 'nokogiri', '~> 1.10.4'
......@@ -234,7 +234,7 @@ gem 'asana', '~> 0.8.1'
gem 'ruby-fogbugz', '~> 0.2.1'
# Kubernetes integration
gem 'kubeclient', '~> 4.2.2'
gem 'kubeclient', '~> 4.4.0'
# Sanitize user input
gem 'sanitize', '~> 4.6'
......
......@@ -284,7 +284,7 @@ GEM
excon (~> 0.58)
formatador (~> 0.2)
mime-types
fog-google (1.8.2)
fog-google (1.9.1)
fog-core (<= 2.1.0)
fog-json (~> 1.2)
fog-xml (~> 0.1.0)
......@@ -505,7 +505,7 @@ GEM
kramdown (2.1.0)
kramdown-parser-gfm (1.1.0)
kramdown (~> 2.0)
kubeclient (4.2.2)
kubeclient (4.4.0)
http (~> 3.0)
recursive-open-struct (~> 1.0, >= 1.0.4)
rest-client (~> 2.0)
......@@ -799,7 +799,7 @@ GEM
retriable (3.1.2)
rinku (2.0.0)
rotp (2.1.2)
rouge (3.7.0)
rouge (3.10.0)
rqrcode (0.7.0)
chunky_png
rqrcode-rails3 (0.1.7)
......@@ -1114,7 +1114,7 @@ DEPENDENCIES
fog-aliyun (~> 0.3)
fog-aws (~> 3.5)
fog-core (= 2.1.0)
fog-google (~> 1.8)
fog-google (~> 1.9)
fog-local (~> 0.6)
fog-openstack (~> 1.0)
fog-rackspace (~> 0.1.1)
......@@ -1164,7 +1164,7 @@ DEPENDENCIES
jwt (~> 2.1.0)
kaminari (~> 1.0)
knapsack (~> 1.17)
kubeclient (~> 4.2.2)
kubeclient (~> 4.4.0)
letter_opener_web (~> 1.3.4)
license_finder (~> 5.4)
licensee (~> 8.9)
......@@ -1229,7 +1229,7 @@ DEPENDENCIES
redis-rails (~> 5.0.2)
request_store (~> 1.3)
responders (~> 2.0)
rouge (~> 3.7)
rouge (~> 3.10)
rqrcode-rails3 (~> 0.1.7)
rspec-parameterized
rspec-rails (~> 3.8.0)
......
<script>
import { mapState, mapGetters, mapActions } from 'vuex';
import { GlLoadingIcon } from '@gitlab/ui';
import statisticsLabels from '../constants';
export default {
components: {
GlLoadingIcon,
},
data() {
return {
statisticsLabels,
};
},
computed: {
...mapState(['isLoading', 'statistics']),
...mapGetters(['getStatistics']),
},
mounted() {
this.fetchStatistics();
},
methods: {
...mapActions(['fetchStatistics']),
},
};
</script>
<template>
<div class="info-well">
<div class="well-segment admin-well admin-well-statistics">
<h4>{{ __('Statistics') }}</h4>
<gl-loading-icon v-if="isLoading" size="md" class="my-3" />
<template v-else>
<p
v-for="statistic in getStatistics(statisticsLabels)"
:key="statistic.key"
class="js-stats"
>
{{ statistic.label }}
<span class="light float-right">{{ statistic.value }}</span>
</p>
</template>
</div>
</div>
</template>
import { s__ } from '~/locale';
const statisticsLabels = {
forks: s__('AdminStatistics|Forks'),
issues: s__('AdminStatistics|Issues'),
mergeRequests: s__('AdminStatistics|Merge Requests'),
notes: s__('AdminStatistics|Notes'),
snippets: s__('AdminStatistics|Snippets'),
sshKeys: s__('AdminStatistics|SSH Keys'),
milestones: s__('AdminStatistics|Milestones'),
activeUsers: s__('AdminStatistics|Active Users'),
};
export default statisticsLabels;
import Vue from 'vue';
import StatisticsPanelApp from './components/app.vue';
import createStore from './store';
export default function(el) {
if (!el) {
return false;
}
const store = createStore();
return new Vue({
el,
store,
components: {
StatisticsPanelApp,
},
render(h) {
return h(StatisticsPanelApp);
},
});
}
import Api from '~/api';
import { s__ } from '~/locale';
import createFlash from '~/flash';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import * as types from './mutation_types';
export const requestStatistics = ({ commit }) => commit(types.REQUEST_STATISTICS);
export const fetchStatistics = ({ dispatch }) => {
dispatch('requestStatistics');
Api.adminStatistics()
.then(({ data }) => {
dispatch('receiveStatisticsSuccess', convertObjectPropsToCamelCase(data, { deep: true }));
})
.catch(error => dispatch('receiveStatisticsError', error));
};
export const receiveStatisticsSuccess = ({ commit }, statistics) =>
commit(types.RECEIVE_STATISTICS_SUCCESS, statistics);
export const receiveStatisticsError = ({ commit }, error) => {
commit(types.RECEIVE_STATISTICS_ERROR, error);
createFlash(s__('AdminDashboard|Error loading the statistics. Please try again'));
};
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
/**
* Merges the statisticsLabels with the state's data
* and returns an array of the following form:
* [{ key: "forks", label: "Forks", value: 50 }]
*/
export const getStatistics = state => labels =>
Object.keys(labels).map(key => {
const result = {
key,
label: labels[key],
value: state.statistics && state.statistics[key] ? state.statistics[key] : null,
};
return result;
});
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
import Vue from 'vue';
import Vuex from 'vuex';
import * as actions from './actions';
import * as getters from './getters';
import mutations from './mutations';
import state from './state';
Vue.use(Vuex);
export default () =>
new Vuex.Store({
actions,
getters,
mutations,
state: state(),
});
export const REQUEST_STATISTICS = 'REQUEST_STATISTICS';
export const RECEIVE_STATISTICS_SUCCESS = 'RECEIVE_STATISTICS_SUCCESS';
export const RECEIVE_STATISTICS_ERROR = 'RECEIVE_STATISTICS_ERROR';
import * as types from './mutation_types';
export default {
[types.REQUEST_STATISTICS](state) {
state.isLoading = true;
},
[types.RECEIVE_STATISTICS_SUCCESS](state, data) {
state.isLoading = false;
state.error = null;
state.statistics = data;
},
[types.RECEIVE_STATISTICS_ERROR](state, error) {
state.isLoading = false;
state.error = error;
},
};
export default () => ({
error: null,
isLoading: false,
statistics: null,
});
......@@ -36,6 +36,7 @@ const Api = {
branchSinglePath: '/api/:version/projects/:id/repository/branches/:branch',
createBranchPath: '/api/:version/projects/:id/repository/branches',
releasesPath: '/api/:version/projects/:id/releases',
adminStatisticsPath: 'api/:version/application/statistics',
group(groupId, callback) {
const url = Api.buildUrl(Api.groupPath).replace(':id', groupId);
......@@ -376,6 +377,11 @@ const Api = {
return axios.get(url);
},
adminStatistics() {
const url = Api.buildUrl(this.adminStatisticsPath);
return axios.get(url);
},
buildUrl(url) {
return joinPaths(gon.relative_url_root || '', url.replace(':version', gon.api_version));
},
......
......@@ -46,7 +46,6 @@ export default class Shortcuts {
$(document).on('click.more_help', '.js-more-help-button', function clickMoreHelp(e) {
$(this).remove();
$('.hidden-shortcut').show();
e.preventDefault();
});
}
......@@ -104,7 +103,6 @@ export default class Shortcuts {
return results;
}
$('.hidden-shortcut').show();
return $('.js-more-help-button').remove();
});
}
......
......@@ -6,7 +6,7 @@ import { CopyAsGFM } from '../markdown/copy_as_gfm';
import { getSelectedFragment } from '~/lib/utils/common_utils';
export default class ShortcutsIssuable extends Shortcuts {
constructor(isMergeRequest) {
constructor() {
super();
Mousetrap.bind('a', () => ShortcutsIssuable.openSidebarDropdown('assignee'));
......@@ -14,12 +14,6 @@ export default class ShortcutsIssuable extends Shortcuts {
Mousetrap.bind('l', () => ShortcutsIssuable.openSidebarDropdown('labels'));
Mousetrap.bind('r', ShortcutsIssuable.replyWithSelectedText);
Mousetrap.bind('e', ShortcutsIssuable.editIssue);
if (isMergeRequest) {
this.enabledHelp.push('.hidden-shortcut.merge_requests');
} else {
this.enabledHelp.push('.hidden-shortcut.issues');
}
}
static replyWithSelectedText() {
......
......@@ -23,7 +23,5 @@ export default class ShortcutsNavigation extends Shortcuts {
Mousetrap.bind('g e', () => findAndFollowLink('.shortcuts-environments'));
Mousetrap.bind('g l', () => findAndFollowLink('.shortcuts-metrics'));
Mousetrap.bind('i', () => findAndFollowLink('.shortcuts-new-issue'));
this.enabledHelp.push('.hidden-shortcut.project');
}
}
......@@ -11,7 +11,5 @@ export default class ShortcutsNetwork extends ShortcutsNavigation {
Mousetrap.bind(['down', 'j'], graph.scrollDown);
Mousetrap.bind(['shift+up', 'shift+k'], graph.scrollTop);
Mousetrap.bind(['shift+down', 'shift+j'], graph.scrollBottom);
this.enabledHelp.push('.hidden-shortcut.network');
}
}
......@@ -6,8 +6,6 @@ export default class ShortcutsWiki extends ShortcutsNavigation {
constructor() {
super();
Mousetrap.bind('e', ShortcutsWiki.editWiki);
this.enabledHelp.push('.hidden-shortcut.wiki');
}
static editWiki() {
......
......@@ -2,9 +2,9 @@
import $ from 'jquery';
import { GlButton } from '@gitlab/ui';
import { getMilestone } from 'ee_else_ce/boards/boards_util';
import ListIssue from 'ee_else_ce/boards/models/issue';
import eventHub from '../eventhub';
import ProjectSelect from './project_select.vue';
import ListIssue from '../models/issue';
import boardsStore from '../stores/boards_store';
export default {
......@@ -54,6 +54,9 @@ export default {
const assignees = this.list.assignee ? [this.list.assignee] : [];
const milestone = getMilestone(this.list);
const { weightFeatureAvailable } = boardsStore;
const { weight } = weightFeatureAvailable ? boardsStore.state.currentBoard : {};
const issue = new ListIssue({
title: this.title,
labels,
......@@ -61,6 +64,7 @@ export default {
assignees,
milestone,
project_id: this.selectedProject.id,
weight,
});
eventHub.$emit(`scroll-board-list-${this.list.id}`);
......
......@@ -245,6 +245,7 @@ export default {
<div
v-if="!loading"
ref="content"
data-qa-selector="boards_dropdown_content"
class="dropdown-content flex-fill"
@scroll.passive="throttledSetScrollFade"
>
......
......@@ -458,7 +458,6 @@ export default {
</div>
</application-row>
<application-row
v-if="isProjectCluster"
id="knative"
:logo-url="knativeLogo"
:title="applications.knative.title"
......
import initAdmin from './admin';
import initAdminStatisticsPanel from '../../admin/statistics_panel/index';
document.addEventListener('DOMContentLoaded', initAdmin());
document.addEventListener('DOMContentLoaded', () => {
const statisticsPanelContainer = document.getElementById('js-admin-statistics-container');
initAdmin();
initAdminStatisticsPanel(statisticsPanelContainer);
});
......@@ -20,6 +20,9 @@ export default {
<stage-column-component
v-for="(stage, index) in graph"
:key="stage.name"
:class="{
'append-right-48': shouldAddRightMargin(index),
}"
:title="capitalizeStageName(stage.name)"
:groups="stage.groups"
:stage-connector-class="stageConnectorClass(index, stage)"
......
......@@ -40,5 +40,15 @@ export default {
refreshPipelineGraph() {
this.$emit('refreshPipelineGraph');
},
/**
* CSS class is applied:
* - if pipeline graph contains only one stage column component
*
* @param {number} index
* @returns {boolean}
*/
shouldAddRightMargin(index) {
return !(index === this.graph.length - 1);
},
},
};
<script>
import { GlToggle } from '@gitlab/ui';
import { capitalizeFirstCharacter } from '~/lib/utils/text_utility';
export default {
name: 'GlToggleVuex',
components: {
GlToggle,
},
props: {
stateProperty: {
type: String,
required: true,
},
storeModule: {
type: String,
required: false,
default: null,
},
setAction: {
type: String,
required: false,
default() {
return `set${capitalizeFirstCharacter(this.stateProperty)}`;
},
},
},
computed: {
value: {
get() {
const { state } = this.$store;
const { stateProperty, storeModule } = this;
return storeModule ? state[storeModule][stateProperty] : state[stateProperty];
},
set(value) {
const { stateProperty, storeModule, setAction } = this;
const action = storeModule ? `${storeModule}/${setAction}` : setAction;
this.$store.dispatch(action, { key: stateProperty, value });
},
},
},
};
</script>
<template>
<gl-toggle v-model="value">
<slot v-bind="{ value }"></slot>
</gl-toggle>
</template>
......@@ -395,6 +395,7 @@ img.emoji {
.prepend-left-default { margin-left: $gl-padding; }
.prepend-left-20 { margin-left: 20px; }
.prepend-left-32 { margin-left: 32px; }
.prepend-left-64 { margin-left: 64px; }
.append-right-4 { margin-right: 4px; }
.append-right-5 { margin-right: 5px; }
.append-right-8 { margin-right: 8px; }
......@@ -402,6 +403,8 @@ img.emoji {
.append-right-15 { margin-right: 15px; }
.append-right-default { margin-right: $gl-padding; }
.append-right-20 { margin-right: 20px; }
.append-right-32 { margin-right: 32px; }
.append-right-48 { margin-right: 48px; }
.prepend-right-32 { margin-right: 32px; }
.append-bottom-0 { margin-bottom: 0; }
.append-bottom-4 { margin-bottom: $gl-padding-4; }
......
......@@ -2,6 +2,12 @@
max-width: 98%;
}
.modal-1040 {
@include media-breakpoint-up(xl) {
max-width: 1040px;
}
}
.modal-header {
background-color: $modal-body-bg;
......
......@@ -476,10 +476,6 @@
display: inline-block;
vertical-align: top;
&:not(:last-child) {
margin-right: 44px;
}
&.left-margin {
&:not(:first-child) {
margin-left: 44px;
......
......@@ -85,7 +85,10 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
params[:application_setting][:import_sources]&.delete("")
params[:application_setting][:restricted_visibility_levels]&.delete("")
# TODO Remove domain_blacklist_raw in APIv5 (See https://gitlab.com/gitlab-org/gitlab-ce/issues/67204)
params.delete(:domain_blacklist_raw) if params[:domain_blacklist_file]
params.delete(:domain_blacklist_raw) if params[:domain_blacklist]
params.delete(:domain_whitelist_raw) if params[:domain_whitelist]
params.require(:application_setting).permit(
visible_application_setting_attributes
......
......@@ -3,8 +3,7 @@
class Admin::DashboardController < Admin::ApplicationController
include CountHelper
COUNTED_ITEMS = [Project, User, Group, ForkNetworkMember, ForkNetwork, Issue,
MergeRequest, Note, Snippet, Key, Milestone].freeze
COUNTED_ITEMS = [Project, User, Group].freeze
# rubocop: disable CodeReuse/ActiveRecord
def index
......
......@@ -31,6 +31,10 @@ class Clusters::BaseController < ApplicationController
access_denied! unless can?(current_user, :create_cluster, clusterable)
end
def authorize_read_prometheus!
access_denied! unless can?(current_user, :read_prometheus, clusterable)
end
def clusterable
raise NotImplementedError
end
......
......@@ -35,6 +35,12 @@ class Clusters::ClustersController < Clusters::BaseController
end
def new
return unless Feature.enabled?(:create_eks_clusters)
@gke_selected = params[:provider] == 'gke'
@eks_selected = params[:provider] == 'eks'
return redirect_to @authorize_url if @gke_selected && @authorize_url && !@valid_gcp_token
end
# Overridding ActionController::Metal#status is NOT a good idea
......@@ -99,7 +105,7 @@ class Clusters::ClustersController < Clusters::BaseController
validate_gcp_token
user_cluster
render :new, locals: { active_tab: 'gcp' }
render :new, locals: { active_tab: 'create' }
end
end
......@@ -116,7 +122,7 @@ class Clusters::ClustersController < Clusters::BaseController
validate_gcp_token
gcp_cluster
render :new, locals: { active_tab: 'user' }
render :new, locals: { active_tab: 'add' }
end
end
......@@ -189,7 +195,8 @@ class Clusters::ClustersController < Clusters::BaseController
end
def generate_gcp_authorize_url
state = generate_session_key_redirect(clusterable.new_path.to_s)
params = Feature.enabled?(:create_eks_clusters) ? { provider: :gke } : {}
state = generate_session_key_redirect(clusterable.new_path(params).to_s)
@authorize_url = GoogleApi::CloudPlatform::Client.new(
nil, callback_google_api_auth_url,
......
......@@ -18,13 +18,26 @@ class Projects::ServicesController < Projects::ApplicationController
def update
@service.attributes = service_params[:service]
if @service.save(context: :manual_change)
redirect_to(project_settings_integrations_path(@project), notice: success_message)
saved = @service.save(context: :manual_change)
respond_to do |format|
format.html do
if saved
redirect_to project_settings_integrations_path(@project),
notice: success_message
else
render 'edit'
end
end
format.json do
status = saved ? :ok : :unprocessable_entity
render json: serialize_as_json, status: status
end
end
end
def test
if @service.can_test?
render json: service_test_response, status: :ok
......@@ -67,4 +80,10 @@ class Projects::ServicesController < Projects::ApplicationController
def ensure_service_enabled
render_404 unless service
end
def serialize_as_json
@service
.as_json(only: @service.json_fields)
.merge(errors: @service.errors.as_json)
end
end
......@@ -20,6 +20,7 @@ class RegistrationsController < Devise::RegistrationsController
super do |new_user|
persist_accepted_terms_if_required(new_user)
yield new_user if block_given?
end
rescue Gitlab::Access::AccessDeniedError
redirect_to(new_user_session_path)
......
......@@ -193,15 +193,30 @@ class IssuableFinder
projects =
if current_user && params[:authorized_only].presence && !current_user_related?
current_user.authorized_projects(min_access_level)
elsif group
find_group_projects
else
Project.public_or_visible_to_user(current_user, min_access_level)
projects_public_or_visible_to_user
end
@projects = projects.with_feature_available_for_user(klass, current_user).reorder(nil) # rubocop: disable CodeReuse/ActiveRecord
end
def projects_public_or_visible_to_user
projects =
if group
if params[:projects]
find_group_projects.id_in(params[:projects])
else
find_group_projects
end
elsif params[:projects]
Project.id_in(params[:projects])
else
Project
end
projects.public_or_visible_to_user(current_user, min_access_level)
end
def find_group_projects
return Project.none unless group
......@@ -209,7 +224,7 @@ class IssuableFinder
Project.where(namespace_id: group.self_and_descendants) # rubocop: disable CodeReuse/ActiveRecord
else
group.projects
end.public_or_visible_to_user(current_user, min_access_level)
end
end
def search
......
......@@ -180,8 +180,12 @@ module ApplicationSettingsHelper
:default_projects_limit,
:default_snippet_visibility,
:disabled_oauth_sign_in_sources,
:domain_blacklist,
:domain_blacklist_enabled,
# TODO Remove domain_blacklist_raw in APIv5 (See https://gitlab.com/gitlab-org/gitlab-ce/issues/67204)
:domain_blacklist_raw,
:domain_whitelist,
# TODO Remove domain_whitelist_raw in APIv5 (See https://gitlab.com/gitlab-org/gitlab-ce/issues/67204)
:domain_whitelist_raw,
:outbound_local_requests_whitelist_raw,
:dsa_key_restriction,
......
# frozen_string_literal: true
module UserCalloutsHelper
GKE_CLUSTER_INTEGRATION = 'gke_cluster_integration'.freeze
GCP_SIGNUP_OFFER = 'gcp_signup_offer'.freeze
SUGGEST_POPOVER_DISMISSED = 'suggest_popover_dismissed'.freeze
GKE_CLUSTER_INTEGRATION = 'gke_cluster_integration'
GCP_SIGNUP_OFFER = 'gcp_signup_offer'
SUGGEST_POPOVER_DISMISSED = 'suggest_popover_dismissed'
def show_gke_cluster_integration_callout?(project)
can?(current_user, :create_cluster, project) &&
......
......@@ -23,6 +23,7 @@ module Ci
belongs_to :auto_canceled_by, class_name: 'Ci::Pipeline'
belongs_to :pipeline_schedule, class_name: 'Ci::PipelineSchedule'
belongs_to :merge_request, class_name: 'MergeRequest'
belongs_to :external_pull_request
has_internal_id :iid, scope: :project, presence: false, init: ->(s) do
s&.project&.all_pipelines&.maximum(:iid) || s&.project&.all_pipelines&.count
......@@ -64,6 +65,11 @@ module Ci
validates :merge_request, presence: { if: :merge_request_event? }
validates :merge_request, absence: { unless: :merge_request_event? }
validates :tag, inclusion: { in: [false], if: :merge_request_event? }
validates :external_pull_request, presence: { if: :external_pull_request_event? }
validates :external_pull_request, absence: { unless: :external_pull_request_event? }
validates :tag, inclusion: { in: [false], if: :external_pull_request_event? }
validates :status, presence: { unless: :importing? }
validate :valid_commit_sha, unless: :importing?
validates :source, exclusion: { in: %w(unknown), unless: :importing? }, on: :create
......@@ -683,6 +689,10 @@ module Ci
variables.append(key: 'CI_MERGE_REQUEST_TARGET_BRANCH_SHA', value: target_sha.to_s)
variables.concat(merge_request.predefined_variables)
end
if external_pull_request_event? && external_pull_request
variables.concat(external_pull_request.predefined_variables)
end
end
end
......
......@@ -23,7 +23,8 @@ module Ci
api: 5,
external: 6,
chat: 8,
merge_request_event: 10
merge_request_event: 10,
external_pull_request_event: 11
}
end
......
......@@ -10,7 +10,6 @@ module Clusters
self.table_name = 'clusters'
PROJECT_ONLY_APPLICATIONS = {
Applications::Knative.application_name => Applications::Knative
}.freeze
APPLICATIONS = {
Applications::Helm.application_name => Applications::Helm,
......@@ -18,7 +17,8 @@ module Clusters
Applications::CertManager.application_name => Applications::CertManager,
Applications::Prometheus.application_name => Applications::Prometheus,
Applications::Runner.application_name => Applications::Runner,
Applications::Jupyter.application_name => Applications::Jupyter
Applications::Jupyter.application_name => Applications::Jupyter,
Applications::Knative.application_name => Applications::Knative
}.merge(PROJECT_ONLY_APPLICATIONS).freeze
DEFAULT_ENVIRONMENT = '*'
KUBE_INGRESS_BASE_DOMAIN = 'KUBE_INGRESS_BASE_DOMAIN'
......
# frozen_string_literal: true
# This model stores pull requests coming from external providers, such as
# GitHub, when GitLab project is set as CI/CD only and remote mirror.
#
# When setting up a remote mirror with GitHub we subscribe to push and
# pull_request webhook events. When a pull request is opened on GitHub,
# a webhook is sent out, we create or update the status of the pull
# request locally.
#
# When the mirror is updated and changes are pushed to branches we check
# if there are open pull requests for the source and target branch.
# If so, we create pipelines for external pull requests.
class ExternalPullRequest < ApplicationRecord
include Gitlab::Utils::StrongMemoize
include ShaAttribute
belongs_to :project
sha_attribute :source_sha
sha_attribute :target_sha
validates :source_branch, presence: true
validates :target_branch, presence: true
validates :source_sha, presence: true
validates :target_sha, presence: true
validates :source_repository, presence: true
validates :target_repository, presence: true
validates :status, presence: true
enum status: {
open: 1,
closed: 2
}
# We currently don't support pull requests from fork, so
# we are going to return an error to the webhook
validate :not_from_fork
scope :by_source_branch, ->(branch) { where(source_branch: branch) }
scope :by_source_repository, -> (repository) { where(source_repository: repository) }
def self.create_or_update_from_params(params)
find_params = params.slice(:project_id, :source_branch, :target_branch)
safe_find_or_initialize_and_update(find: find_params, update: params) do |pull_request|
yield(pull_request) if block_given?
end
end
def actual_branch_head?
actual_source_branch_sha == source_sha
end
def from_fork?
source_repository != target_repository
end
def source_ref
Gitlab::Git::BRANCH_REF_PREFIX + source_branch
end
def predefined_variables
Gitlab::Ci::Variables::Collection.new.tap do |variables|
variables.append(key: 'CI_EXTERNAL_PULL_REQUEST_IID', value: pull_request_iid.to_s)
variables.append(key: 'CI_EXTERNAL_PULL_REQUEST_SOURCE_BRANCH_SHA', value: source_sha)
variables.append(key: 'CI_EXTERNAL_PULL_REQUEST_TARGET_BRANCH_SHA', value: target_sha)
variables.append(key: 'CI_EXTERNAL_PULL_REQUEST_SOURCE_BRANCH_NAME', value: source_branch)
variables.append(key: 'CI_EXTERNAL_PULL_REQUEST_TARGET_BRANCH_NAME', value: target_branch)
end
end
private
def actual_source_branch_sha
project.commit(source_ref)&.sha
end
def not_from_fork
if from_fork?
errors.add(:base, 'Pull requests from fork are not supported')
end
end
def self.safe_find_or_initialize_and_update(find:, update:)
safe_ensure_unique(retries: 1) do
model = find_or_initialize_by(find)
if model.update(update)
yield(model) if block_given?
end
model
end
end
end
......@@ -31,7 +31,7 @@ class Issue < ApplicationRecord
has_internal_id :iid, scope: :project, init: ->(s) { s&.project&.issues&.maximum(:iid) }
has_many :events, as: :target, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :events, as: :target, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
has_many :merge_requests_closing_issues,
class_name: 'MergeRequestsClosingIssues',
......
......@@ -54,7 +54,7 @@ class MergeRequest < ApplicationRecord
belongs_to :head_pipeline, foreign_key: "head_pipeline_id", class_name: "Ci::Pipeline"
has_many :events, as: :target, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :events, as: :target, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
has_many :merge_requests_closing_issues,
class_name: 'MergeRequestsClosingIssues',
......
......@@ -37,7 +37,7 @@ class Milestone < ApplicationRecord
has_many :issues
has_many :labels, -> { distinct.reorder('labels.title') }, through: :issues
has_many :merge_requests
has_many :events, as: :target, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :events, as: :target, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
scope :of_projects, ->(ids) { where(project_id: ids) }
scope :of_groups, ->(ids) { where(group_id: ids) }
......
......@@ -78,7 +78,7 @@ class Note < ApplicationRecord
# suggestions.delete_all calls
has_many :suggestions, -> { order(:relative_order) },
inverse_of: :note, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
has_many :events, as: :target, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :events, as: :target, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
has_one :system_note_metadata
has_one :note_diff_file, inverse_of: :diff_note, foreign_key: :diff_note_id
......
......@@ -17,7 +17,7 @@ class PagesDomain < ApplicationRecord
validates :certificate, certificate: true, if: ->(domain) { domain.certificate.present? }
validates :key, presence: { message: 'must be present if HTTPS-only is enabled' },
if: :certificate_should_be_present?
validates :key, certificate_key: true, if: ->(domain) { domain.key.present? }
validates :key, certificate_key: true, named_ecdsa_key: true, if: ->(domain) { domain.key.present? }
validates :verification_code, presence: true, allow_blank: false
validate :validate_pages_domain
......@@ -247,7 +247,7 @@ class PagesDomain < ApplicationRecord
def pkey
return unless key
@pkey ||= OpenSSL::PKey::RSA.new(key)
@pkey ||= OpenSSL::PKey.read(key)
rescue OpenSSL::PKey::PKeyError, OpenSSL::Cipher::CipherError
nil
end
......
......@@ -291,6 +291,8 @@ class Project < ApplicationRecord
has_many :remote_mirrors, inverse_of: :project
has_many :cycle_analytics_stages, class_name: 'Analytics::CycleAnalytics::ProjectStage'
has_many :external_pull_requests, inverse_of: :project
accepts_nested_attributes_for :variables, allow_destroy: true
accepts_nested_attributes_for :project_feature, update_only: true
accepts_nested_attributes_for :import_data
......
......@@ -107,6 +107,13 @@ class Service < ApplicationRecord
[]
end
# Expose a list of fields in the JSON endpoint.
#
# This list is used in `Service#as_json(only: json_fields)`.
def json_fields
%w(active)
end
def test_data(project, user)
Gitlab::DataBuilder::Push.build_sample(project, user)
end
......
......@@ -131,7 +131,7 @@ class User < ApplicationRecord
has_many :notes, dependent: :destroy, foreign_key: :author_id # rubocop:disable Cop/ActiveRecordDependent
has_many :issues, dependent: :destroy, foreign_key: :author_id # rubocop:disable Cop/ActiveRecordDependent
has_many :merge_requests, dependent: :destroy, foreign_key: :author_id # rubocop:disable Cop/ActiveRecordDependent
has_many :events, dependent: :destroy, foreign_key: :author_id # rubocop:disable Cop/ActiveRecordDependent
has_many :events, dependent: :delete_all, foreign_key: :author_id # rubocop:disable Cop/ActiveRecordDependent
has_many :releases, dependent: :nullify, foreign_key: :author_id # rubocop:disable Cop/ActiveRecordDependent
has_many :subscriptions, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :oauth_applications, class_name: 'Doorkeeper::Application', as: :owner, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
......
......@@ -8,6 +8,7 @@ module Clusters
enable :create_cluster
enable :update_cluster
enable :admin_cluster
enable :read_prometheus
end
end
end
......@@ -25,8 +25,8 @@ class ClusterablePresenter < Gitlab::View::Presenter::Delegated
polymorphic_path([clusterable, :clusters])
end
def new_path
new_polymorphic_path([clusterable, :cluster])
def new_path(options = {})
new_polymorphic_path([clusterable, :cluster], options)
end
def create_user_clusters_path
......
......@@ -18,8 +18,8 @@ class InstanceClusterablePresenter < ClusterablePresenter
end
override :new_path
def new_path
new_admin_cluster_path
def new_path(options = {})
new_admin_cluster_path(options)
end
override :cluster_status_cluster_path
......
# frozen_string_literal: true
class MergeRequestNoteableEntity < Grape::Entity
class MergeRequestNoteableEntity < IssuableEntity
include RequestAwareEntity
# Currently this attr is exposed to be used in app/assets/javascripts/notes/stores/getters.js
......
......@@ -18,7 +18,8 @@ module Ci
Gitlab::Ci::Pipeline::Chain::Limit::Activity,
Gitlab::Ci::Pipeline::Chain::Limit::JobActivity].freeze
def execute(source, ignore_skip_ci: false, save_on_errors: true, trigger_request: nil, schedule: nil, merge_request: nil, **options, &block)
# rubocop: disable Metrics/ParameterLists
def execute(source, ignore_skip_ci: false, save_on_errors: true, trigger_request: nil, schedule: nil, merge_request: nil, external_pull_request: nil, **options, &block)
@pipeline = Ci::Pipeline.new
command = Gitlab::Ci::Pipeline::Chain::Command.new(
......@@ -32,6 +33,7 @@ module Ci
trigger_request: trigger_request,
schedule: schedule,
merge_request: merge_request,
external_pull_request: external_pull_request,
ignore_skip_ci: ignore_skip_ci,
save_incompleted: save_on_errors,
seeds_block: block,
......@@ -62,6 +64,7 @@ module Ci
pipeline
end
# rubocop: enable Metrics/ParameterLists
def execute!(*args, &block)
execute(*args, &block).tap do |pipeline|
......
# frozen_string_literal: true
# This service is responsible for creating a pipeline for a given
# ExternalPullRequest coming from other providers such as GitHub.
module ExternalPullRequests
class CreatePipelineService < BaseService
def execute(pull_request)
return unless pull_request.open? && pull_request.actual_branch_head?
create_pipeline_for(pull_request)
end
private
def create_pipeline_for(pull_request)
Ci::CreatePipelineService.new(project, current_user, create_params(pull_request))
.execute(:external_pull_request_event, external_pull_request: pull_request)
end
def create_params(pull_request)
{
ref: pull_request.source_ref,
source_sha: pull_request.source_sha,
target_sha: pull_request.target_sha
}
end
end
end
......@@ -57,7 +57,9 @@ module Git
Ci::CreatePipelineService
.new(project, current_user, pipeline_params)
.execute(:push, pipeline_options)
.execute!(:push, pipeline_options)
rescue Ci::CreatePipelineService::CreateError => ex
log_pipeline_errors(ex)
end
def execute_project_hooks
......@@ -125,5 +127,29 @@ module Git
project.mark_stuck_remote_mirrors_as_failed!
project.update_remote_mirrors
end
def log_pipeline_errors(exception)
data = {
class: self.class.name,
correlation_id: Labkit::Correlation::CorrelationId.current_id.to_s,
project_id: project.id,
project_path: project.full_path,
message: "Error creating pipeline",
errors: exception.to_s,
pipeline_params: pipeline_params
}
logger.warn(data)
end
def logger
if Sidekiq.server?
Sidekiq.logger
else
# This service runs in Sidekiq, so this shouldn't ever be
# called, but this is included just in case.
Gitlab::ProjectServiceLogger
end
end
end
end
# frozen_string_literal: true
# UrlValidator
# CertificateKeyValidator
#
# Custom validator for private keys.
#
......@@ -20,7 +20,7 @@ class CertificateKeyValidator < ActiveModel::EachValidator
def valid_private_key_pem?(value)
return false unless value
pkey = OpenSSL::PKey::RSA.new(value)
pkey = OpenSSL::PKey.read(value)
pkey.private?
rescue OpenSSL::PKey::PKeyError
false
......
# frozen_string_literal: true
# NamedEcdsaKeyValidator
#
# Custom validator for ecdsa private keys.
# Golang currently doesn't support explicit curves for ECDSA certificates
# This validator checks if curve is set by name, not by parameters
#
# class Project < ActiveRecord::Base
# validates :certificate_key, named_ecdsa_key: true
# end
#
class NamedEcdsaKeyValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
if explicit_ec?(value)
record.errors.add(attribute, "ECDSA keys with explicit curves are not supported")
end
end
private
UNNAMED_CURVE = "UNDEF"
def explicit_ec?(value)
return false unless value
pkey = OpenSSL::PKey.read(value)
return false unless pkey.is_a?(OpenSSL::PKey::EC)
pkey.group.curve_name == UNNAMED_CURVE
rescue OpenSSL::PKey::PKeyError
false
end
end
......@@ -35,41 +35,7 @@
= link_to 'New group', new_admin_group_path, class: "btn btn-success"
.row
.col-md-4
.info-well
.well-segment.admin-well.admin-well-statistics
%h4 Statistics
%p
Forks
%span.light.float-right
= approximate_fork_count_with_delimiters(@counts)
%p
Issues
%span.light.float-right
= approximate_count_with_delimiters(@counts, Issue)
%p
Merge Requests
%span.light.float-right
= approximate_count_with_delimiters(@counts, MergeRequest)
%p
Notes
%span.light.float-right
= approximate_count_with_delimiters(@counts, Note)
%p
Snippets
%span.light.float-right
= approximate_count_with_delimiters(@counts, Snippet)
%p
SSH Keys
%span.light.float-right
= approximate_count_with_delimiters(@counts, Key)
%p
Milestones
%span.light.float-right
= approximate_count_with_delimiters(@counts, Milestone)
%p
Active Users
%span.light.float-right
= number_with_delimiter(User.active.count)
#js-admin-statistics-container
.col-md-4
.info-well
.well-segment.admin-well.admin-well-features
......
- provider = local_assigns.fetch(:provider)
- logo_path = local_assigns.fetch(:logo_path)
- label = local_assigns.fetch(:label)
= link_to clusterable.new_path(provider: provider), class: 'btn gl-button btn-outline flex-fill d-inline-flex flex-column mr-3 justify-content-center align-items-center' do
= image_tag logo_path, alt: label, class: 'gl-w-13 gl-h-13'
%span
= label
- gke_label = s_('ClusterIntegration|Google GKE')
- eks_label = s_('ClusterIntegration|Amazon EKS')
- create_cluster_label = s_('ClusterIntegration|Create cluster on')
.d-flex.flex-column
%h5
= create_cluster_label
.d-flex
= render partial: 'clusters/clusters/cloud_providers/cloud_provider_button',
locals: { provider: 'gke', label: gke_label, logo_path: '' }
= render partial: 'clusters/clusters/cloud_providers/cloud_provider_button',
locals: { provider: 'eks', label: eks_label, logo_path: '' }
.js-create-eks-cluster-form-container
- breadcrumb_title _('Kubernetes')
- page_title _('Kubernetes Cluster')
- active_tab = local_assigns.fetch(:active_tab, 'gcp')
- create_eks_enabled = Feature.enabled?(:create_eks_clusters)
- active_tab = local_assigns.fetch(:active_tab, 'create')
- link_end = '<a/>'.html_safe
= javascript_include_tag 'https://apis.google.com/js/api.js'
= render_gcp_signup_offer
......@@ -11,26 +13,36 @@
.col-md-9.js-toggle-container
%ul.nav-links.nav-tabs.gitlab-tabs.nav{ role: 'tablist' }
%li.nav-item{ role: 'presentation' }
%a.nav-link{ href: '#create-gcp-cluster-pane', id: 'create-gcp-cluster-tab', class: active_when(active_tab == 'gcp'), data: { toggle: 'tab' }, role: 'tab' }
%a.nav-link{ href: '#create-cluster-pane', id: 'create-cluster-tab', class: active_when(active_tab == 'create'), data: { toggle: 'tab' }, role: 'tab' }
%span Create new Cluster on GKE
%li.nav-item{ role: 'presentation' }
%a.nav-link{ href: '#add-user-cluster-pane', id: 'add-user-cluster-tab', class: active_when(active_tab == 'user'), data: { toggle: 'tab' }, role: 'tab' }
%a.nav-link{ href: '#add-cluster-pane', id: 'add-cluster-tab', class: active_when(active_tab == 'add'), data: { toggle: 'tab' }, role: 'tab' }
%span Add existing cluster
.tab-content.gitlab-tab-content
.tab-pane{ id: 'create-gcp-cluster-pane', class: active_when(active_tab == 'gcp'), role: 'tabpanel' }
- if create_eks_enabled
.tab-pane{ id: 'create-cluster-pane', class: active_when(active_tab == 'create'), role: 'tabpanel' }
- if @gke_selected && @valid_gcp_token
= render 'clusters/clusters/gcp/header'
= render 'clusters/clusters/gcp/form'
- elsif @eks_selected
= render 'clusters/clusters/eks/index'
- else
= render 'clusters/clusters/cloud_providers/cloud_provider_selector'
- else
.tab-pane{ id: 'create-cluster-pane', class: active_when(active_tab == 'create'), role: 'tabpanel' }
= render 'clusters/clusters/gcp/header'
- if @valid_gcp_token
= render 'clusters/clusters/gcp/form'
- elsif @authorize_url
.signin-with-google
= link_to(image_tag('auth_buttons/signin_with_google.png', width: '191px'), @authorize_url)
= _('or')
= link_to('create a new Google account', 'https://accounts.google.com/SignUpWithoutGmail?service=cloudconsole&continue=https%3A%2F%2Fconsole.cloud.google.com%2Ffreetrial%3Futm_campaign%3D2018_cpanel%26utm_source%3Dgitlab%26utm_medium%3Dreferral', target: '_blank', rel: 'noopener noreferrer')
- create_account_link = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: 'https://accounts.google.com/SignUpWithoutGmail?service=cloudconsole&continue=https%3A%2F%2Fconsole.cloud.google.com%2Ffreetrial%3Futm_campaign%3D2018_cpanel%26utm_source%3Dgitlab%26utm_medium%3Dreferral' }
= link_to(image_tag('auth_buttons/signin_with_google.png', width: '191px', alt: _('Sign in with Google')), @authorize_url)
= s_('or %{link_start}create a new Google account%{link_end}').html_safe % { link_start: create_account_link, link_end: link_end }
- else
- link = link_to(s_('ClusterIntegration|properly configured'), help_page_path("integration/google"), target: '_blank', rel: 'noopener noreferrer')
= s_('Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service.').html_safe % { link_to_documentation: link }
- documentation_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: help_page_path("integration/google") }
= s_('Google authentication is not %{link_start}property configured%{link_end}. Ask your GitLab administrator if you want to use this service.').html_safe % { link_start: documentation_link_start, link_end: link_end }
.tab-pane{ id: 'add-user-cluster-pane', class: active_when(active_tab == 'user'), role: 'tabpanel' }
.tab-pane{ id: 'add-cluster-pane', class: active_when(active_tab == 'add'), role: 'tabpanel' }
= render 'clusters/clusters/user/header'
= render 'clusters/clusters/user/form'
This diff is collapsed.
......@@ -48,14 +48,14 @@
- if group_sidebar_link?(:issues)
= nav_link(path: group_issues_sub_menu_items) do
= link_to issues_group_path(@group) do
= link_to issues_group_path(@group), data: { qa_selector: 'group_issues_item' } do
.nav-icon-container
= sprite_icon('issues')
%span.nav-item-name
= _('Issues')
%span.badge.badge-pill.count= number_with_delimiter(issues_count)
%ul.sidebar-sub-level-items
%ul.sidebar-sub-level-items{ data: { qa_selector: 'group_issues_sidebar_submenu'} }
= nav_link(path: ['groups#issues', 'labels#index', 'milestones#index'], html_options: { class: "fly-out-top-item" } ) do
= link_to issues_group_path(@group) do
%strong.fly-out-top-item-name
......@@ -70,7 +70,7 @@
- if group_sidebar_link?(:boards)
= nav_link(path: ['boards#index', 'boards#show']) do
= link_to group_boards_path(@group), title: boards_link_text do
= link_to group_boards_path(@group), title: boards_link_text, data: { qa_selector: 'group_issue_boards_link' } do
%span
= boards_link_text
......
......@@ -8,7 +8,7 @@
- else
- can_create_fork = current_user.can?(:create_fork)
= link_to new_project_fork_path(@project),
class: "btn btn-default has-tooltip count-badge-button d-flex align-items-center fork-btn #{'has-tooltip disabled' unless can_create_fork}",
class: "btn btn-default btn-xs has-tooltip count-badge-button d-flex align-items-center fork-btn #{'has-tooltip disabled' unless can_create_fork}",
title: (s_('ProjectOverview|You have reached your project limit') unless can_create_fork) do
= sprite_icon('fork', { css_class: 'icon' })
%span= s_('ProjectOverview|Fork')
......
......@@ -13,12 +13,13 @@
%pre.dark#merge-info-1
- if @merge_request.for_fork?
:preserve
git fetch #{h default_url_to_repo(@merge_request.source_project)} #{h @merge_request.source_branch}
git checkout -b #{h @merge_request.source_project_path}-#{h @merge_request.source_branch} FETCH_HEAD
-# All repo/branch refs have been quoted to allow support for special characters (such as #my-branch)
git fetch "#{h default_url_to_repo(@merge_request.source_project)}" "#{h @merge_request.source_branch}"
git checkout -b "#{h @merge_request.source_project_path}-#{h @merge_request.source_branch}" FETCH_HEAD
- else
:preserve
git fetch origin
git checkout -b #{h @merge_request.source_branch} origin/#{h @merge_request.source_branch}
git checkout -b "#{h @merge_request.source_branch}" "origin/#{h @merge_request.source_branch}"
%p
%strong Step 2.
Review the changes locally
......@@ -31,20 +32,20 @@
- if @merge_request.for_fork?
:preserve
git fetch origin
git checkout origin/#{h @merge_request.target_branch}
git merge --no-ff #{h @merge_request.source_project_path}-#{h @merge_request.source_branch}
git checkout "origin/#{h @merge_request.target_branch}"
git merge --no-ff "#{h @merge_request.source_project_path}-#{h @merge_request.source_branch}"
- else
:preserve
git fetch origin
git checkout origin/#{h @merge_request.target_branch}
git merge --no-ff #{h @merge_request.source_branch}
git checkout "origin/#{h @merge_request.target_branch}"
git merge --no-ff "#{h @merge_request.source_branch}"
%p
%strong Step 4.
Push the result of the merge to GitLab
= clipboard_button(target: "pre#merge-info-4", title: "Copy commands to clipboard")
%pre.dark#merge-info-4
:preserve
git push origin #{h @merge_request.target_branch}
git push origin "#{h @merge_request.target_branch}"
- unless @merge_request.can_be_merged_by?(current_user)
%p
Note that pushing to GitLab requires write access to this repository.
......
= label_tag :sort_by, 'Sort by', class: 'col-form-label label-bold pr-2'
= label_tag :sort_by, 'Sort by', class: 'col-form-label label-bold px-2'
.dropdown.inline.qa-user-sort-dropdown
= dropdown_toggle(member_sort_options_hash[@sort], { toggle: 'dropdown' })
%ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable
......
......@@ -17,14 +17,14 @@
.js-notification-toggle-btns
%div{ class: ("btn-group" if notification_setting.custom?) }
- if notification_setting.custom?
%button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn.text-left#notifications-button{ type: "button", title: button_title, class: "#{btn_class}", "aria-label" => aria_label, data: { container: "body", toggle: "modal", target: "#" + notifications_menu_identifier("modal", notification_setting), display: 'static' } }
%button.dropdown-new.btn.btn-default.btn-xs.has-tooltip.notifications-btn.text-left#notifications-button{ type: "button", title: button_title, class: "#{btn_class}", "aria-label" => aria_label, data: { container: "body", toggle: "modal", target: "#" + notifications_menu_identifier("modal", notification_setting), display: 'static' } }
= icon("bell", class: "js-notification-loading")
= notification_title(notification_setting.level)
%button.btn.dropdown-toggle{ data: { toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" } }
= icon('caret-down')
.sr-only Toggle dropdown
- else
%button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn#notifications-button{ type: "button", title: button_title, class: "#{btn_class}", "aria-label" => aria_label, data: { container: "body", toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" } }
%button.dropdown-new.btn.btn-default.btn-xs.has-tooltip.notifications-btn#notifications-button{ type: "button", title: button_title, class: "#{btn_class}", "aria-label" => aria_label, data: { container: "body", toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" } }
.float-left
= icon("bell", class: "js-notification-loading")
= notification_title(notification_setting.level)
......
......@@ -160,6 +160,7 @@
- repository_import
- repository_remove_remote
- system_hook_push
- update_external_pull_requests
- update_merge_requests
- update_project_statistics
- upload_checksum
......
# frozen_string_literal: true
class UpdateExternalPullRequestsWorker
include ApplicationWorker
def perform(project_id, user_id, ref)
project = Project.find_by_id(project_id)
return unless project
user = User.find_by_id(user_id)
return unless user
branch = Gitlab::Git.branch_name(ref)
return unless branch
external_pull_requests = project.external_pull_requests
.by_source_repository(project.import_source)
.by_source_branch(branch)
external_pull_requests.find_each do |pull_request|
ExternalPullRequests::CreatePipelineService.new(project, user)
.execute(pull_request)
end
end
end
---
title: Only show /copy_metadata quick action when usable
merge_request: 31735
author: Lee Tickett
type: fixed
---
title: Bump Kubeclient to 4.4.0
merge_request: 32811
author:
type: other
---
title: 'Admin dashboard: Fetch and render statistics async'
merge_request: 32449
author:
type: other
---
title: Fix watch button styling and notifications buttons consistency
merge_request: 32827
author:
type: fixed
---
title: Update merge train documentation
merge_request: 32218
author:
type: changed
---
title: Preprocess wiki attachments with GitLab-Workhorse
merge_request: 32663
author:
type: performance
---
title: Improve application settings API
merge_request: 31149
author: Mathieu Parent
type: fixed
---
title: Allow ECDSA certificates for pages domains
merge_request: 32393
author:
type: added
---
title: Lower search counters
merge_request: 11777
author:
type: performance
---
title: Upgrade to Gitaly v1.62.0
merge_request: 32608
author:
type: changed
---
title: Fix sharing localStorage with all MRs
merge_request: 32699
author:
type: fixed
---
title: Allow Knative to be installed on group and instance level clusters
merge_request: 32128
author:
type: added
---
title: Passing job rules downstream and E2E specs for job:rules configuration
merge_request: 32609
author:
type: fixed
---
title: Reduce N+1 when doing project export
merge_request: 32423
author:
type: performance
---
title: Clean up keyboard shortcuts help modal, removing and adding as needed
merge_request: 31642
author:
type: other
---
title: Expose update project service endpoint JSON
merge_request: 32759
author:
type: added
---
title: Quote branch names in how to merge instructions
merge_request: 32639
author: Lee Tickett
type: fixed
---
title: Add padding to left of "Sort by" in members dropdown
merge_request: 32602
author:
type: other
---
title: Log errors for failed pipeline creation in PostReceive
merge_request: 32633
author:
type: other
---
title: Update rouge to v3.10.0
merge_request: 32745
author:
type: other
---
title: Prevent empty external authorization classification labels from overriding
the default label
merge_request: 32517
author: Will Chandler
type: fixed
......@@ -321,6 +321,9 @@ production: &base
# external_https: ["1.1.1.1:443", "[2001::1]:443"] # If defined, enables custom domain and certificate support in GitLab Pages
admin:
address: unix:/home/git/gitlab/tmp/sockets/private/pages-admin.socket # TCP connections are supported too (e.g. tcp://host:port)
# File that contains the shared secret key for verifying access for gitlab-pages.
# Default is '.gitlab_pages_shared_secret' relative to Rails.root (i.e. root of the GitLab app).
# secret_file: /home/git/gitlab/.gitlab_pages_shared_secret
## Mattermost
## For enabling Add to Mattermost button
......
......@@ -292,6 +292,7 @@ Settings.pages['artifacts_server'] ||= Settings.pages['enabled'] if Settings.pa
Settings.pages['admin'] ||= Settingslogic.new({})
Settings.pages.admin['certificate'] ||= ''
Settings.pages['secret_file'] ||= Rails.root.join('.gitlab_pages_shared_secret')
#
# Geo
......
......@@ -115,3 +115,4 @@
- [export_csv, 1]
- [incident_management, 2]
- [jira_connect, 1]
- [update_external_pull_requests, 3]
......@@ -3,8 +3,23 @@
class RemoveEpicIssuesDefaultRelativePosition < ActiveRecord::Migration[5.2]
DOWNTIME = false
def change
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
def up
# The column won't exist if someone installed EE, downgraded to CE
# before it was added in EE, then tries to upgrade CE.
if column_exists?(:epic_issues, :relative_position)
change_column_null :epic_issues, :relative_position, true
change_column_default :epic_issues, :relative_position, from: 1073741823, to: nil
else
add_column_with_default(:epic_issues, :relative_position, :integer, default: nil, allow_null: true)
end
end
def down
change_column_default :epic_issues, :relative_position, from: nil, to: 1073741823
change_column_null :epic_issues, :relative_position, false
end
end
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment