Commit a5ab3467 authored by 🤖 GitLab Bot 🤖's avatar 🤖 GitLab Bot 🤖

Add latest changes from gitlab-org/[email protected]

parent eb30dd6e
......@@ -3,9 +3,12 @@
import Members from 'ee_else_ce/members';
import memberExpirationDate from '~/member_expiration_date';
import UsersSelect from '~/users_select';
import groupsSelect from '~/groups_select';
document.addEventListener('DOMContentLoaded', () => {
memberExpirationDate();
memberExpirationDate('.js-access-expiration-date-groups');
new Members();
groupsSelect();
new UsersSelect();
});
<script>
import { mapActions } from 'vuex';
import { GlFormGroup, GlToggle, GlFormSelect, GlFormTextarea, GlButton } from '@gitlab/ui';
import { mapActions, mapState } from 'vuex';
import { GlFormGroup, GlToggle, GlFormSelect, GlFormTextarea, GlButton, GlCard } from '@gitlab/ui';
import { s__, __, sprintf } from '~/locale';
import { NAME_REGEX_LENGTH } from '../constants';
import { mapComputed } from '~/vuex_shared/bindings';
......@@ -12,19 +12,25 @@ export default {
GlFormSelect,
GlFormTextarea,
GlButton,
GlCard,
},
labelsConfig: {
cols: 3,
align: 'right',
},
computed: {
...mapComputed('settings', 'updateSettings', [
'enabled',
'cadence',
'older_than',
'keep_n',
'name_regex',
]),
...mapState(['formOptions']),
...mapComputed(
[
'enabled',
{ key: 'cadence', getter: 'getCadence' },
{ key: 'older_than', getter: 'getOlderThan' },
{ key: 'keep_n', getter: 'getKeepN' },
'name_regex',
],
'updateSettings',
'settings',
),
policyEnabledText() {
return this.enabled ? __('enabled') : __('disabled');
},
......@@ -66,12 +72,12 @@ export default {
</script>
<template>
<div class="card">
<form ref="form-element" @submit.prevent="saveSettings" @reset.prevent="resetSettings">
<div class="card-header">
<form ref="form-element" @submit.prevent="saveSettings" @reset.prevent="resetSettings">
<gl-card>
<template #header>
{{ s__('ContainerRegistry|Tag expiration policy') }}
</div>
<div class="card-body">
</template>
<template>
<gl-form-group
id="expiration-policy-toggle-group"
:label-cols="$options.labelsConfig.cols"
......@@ -92,9 +98,10 @@ export default {
label-for="expiration-policy-interval"
:label="s__('ContainerRegistry|Expiration interval:')"
>
<gl-form-select id="expiration-policy-interval" v-model="older_than">
<option value="1">{{ __('Option 1') }}</option>
<option value="2">{{ __('Option 2') }}</option>
<gl-form-select id="expiration-policy-interval" v-model="older_than" :disabled="!enabled">
<option v-for="option in formOptions.olderThan" :key="option.key" :value="option.key">
{{ option.label }}
</option>
</gl-form-select>
</gl-form-group>
......@@ -105,9 +112,10 @@ export default {
label-for="expiration-policy-schedule"
:label="s__('ContainerRegistry|Expiration schedule:')"
>
<gl-form-select id="expiration-policy-schedule" v-model="cadence">
<option value="1">{{ __('Option 1') }}</option>
<option value="2">{{ __('Option 2') }}</option>
<gl-form-select id="expiration-policy-schedule" v-model="cadence" :disabled="!enabled">
<option v-for="option in formOptions.cadence" :key="option.key" :value="option.key">
{{ option.label }}
</option>
</gl-form-select>
</gl-form-group>
......@@ -118,9 +126,10 @@ export default {
label-for="expiration-policy-latest"
:label="s__('ContainerRegistry|Expiration latest:')"
>
<gl-form-select id="expiration-policy-latest" v-model="keep_n">
<option value="1">{{ __('Option 1') }}</option>
<option value="2">{{ __('Option 2') }}</option>
<gl-form-select id="expiration-policy-latest" v-model="keep_n" :disabled="!enabled">
<option v-for="option in formOptions.keepN" :key="option.key" :value="option.key">
{{ option.label }}
</option>
</gl-form-select>
</gl-form-group>
......@@ -140,19 +149,30 @@ export default {
v-model="name_regex"
:placeholder="nameRegexPlaceholder"
:state="nameRegexState"
:disabled="!enabled"
trim
/>
<template #description>
<span ref="regex-description" v-html="regexHelpText"></span>
</template>
</gl-form-group>
</div>
<div class="card-footer text-right">
<gl-button ref="cancel-button" type="reset">{{ __('Cancel') }}</gl-button>
<gl-button ref="save-button" type="submit" :disabled="formIsValid" variant="success">
{{ __('Save Expiration Policy') }}
</gl-button>
</div>
</form>
</div>
</template>
<template #footer>
<div class="d-flex justify-content-end">
<gl-button ref="cancel-button" type="reset" class="mr-2 d-block">{{
__('Cancel')
}}</gl-button>
<gl-button
ref="save-button"
type="submit"
:disabled="formIsValid"
variant="success"
class="d-block"
>
{{ __('Save expiration policy') }}
</gl-button>
</div>
</template>
</gl-card>
</form>
</template>
import { findDefaultOption } from '../utils';
export const getCadence = state =>
state.settings.cadence || findDefaultOption(state.formOptions.cadence);
export const getKeepN = state =>
state.settings.keep_n || findDefaultOption(state.formOptions.keepN);
export const getOlderThan = state =>
state.settings.older_than || findDefaultOption(state.formOptions.olderThan);
......@@ -2,6 +2,7 @@ import Vue from 'vue';
import Vuex from 'vuex';
import * as actions from './actions';
import mutations from './mutations';
import * as getters from './getters';
import state from './state';
Vue.use(Vuex);
......@@ -11,6 +12,7 @@ export const createStore = () =>
state,
actions,
mutations,
getters,
});
export default createStore();
......@@ -3,6 +3,11 @@ import * as types from './mutation_types';
export default {
[types.SET_INITIAL_STATE](state, initialState) {
state.projectId = initialState.projectId;
state.formOptions = {
cadence: JSON.parse(initialState.cadenceOptions),
keepN: JSON.parse(initialState.keepNOptions),
olderThan: JSON.parse(initialState.olderThanOptions),
};
},
[types.UPDATE_SETTINGS](state, settings) {
state.settings = { ...state.settings, ...settings };
......
......@@ -23,4 +23,8 @@ export default () => ({
* Same structure as settings, above but Frozen object and used only in case the user clicks 'cancel'
*/
original: {},
/*
* Contains the options used to populate the form selects
*/
formOptions: {},
});
export const findDefaultOption = options => {
const item = options.find(o => o.default);
return item ? item.key : null;
};
export default () => {};
export const mapComputed = (root, updateFn, list) => {
/**
* Returns computed properties two way bound to vuex
*
* @param {(string[]|Object[])} list - list of string matching state keys or list objects
* @param {string} list[].key - the key matching the key present in the vuex state
* @param {string} list[].getter - the name of the getter, leave it empty to not use a getter
* @param {string} list[].updateFn - the name of the action, leave it empty to use the default action
* @param {string} defaultUpdateFn - the default function to dispatch
* @param {string} root - the key of the state where to search fo they keys described in list
* @returns {Object} a dictionary with all the computed properties generated
*/
export const mapComputed = (list, defaultUpdateFn, root) => {
const result = {};
list.forEach(key => {
list.forEach(item => {
const [getter, key, updateFn] =
typeof item === 'string'
? [false, item, defaultUpdateFn]
: [item.getter, item.key, item.updateFn || defaultUpdateFn];
result[key] = {
get() {
return this.$store.state[root][key];
if (getter) {
return this.$store.getters[getter];
} else if (root) {
return this.$store.state[root][key];
}
return this.$store.state[key];
},
set(value) {
this.$store.dispatch(updateFn, { [key]: value });
......
......@@ -3,7 +3,7 @@
border-bottom: 1px solid $border-color;
}
.users-project-form {
.invite-users-form {
.btn-success {
margin-right: 10px;
}
......
......@@ -5,6 +5,12 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
before_action :set_application_setting
before_action :whitelist_query_limiting, only: [:usage_data]
before_action :validate_self_monitoring_feature_flag_enabled, only: [
:create_self_monitoring_project,
:status_create_self_monitoring_project,
:delete_self_monitoring_project,
:status_delete_self_monitoring_project
]
before_action do
push_frontend_feature_flag(:self_monitoring_project)
......@@ -74,8 +80,6 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
end
def create_self_monitoring_project
return self_monitoring_project_not_implemented unless Feature.enabled?(:self_monitoring_project)
job_id = SelfMonitoringProjectCreateWorker.perform_async
render status: :accepted, json: {
......@@ -85,8 +89,6 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
end
def status_create_self_monitoring_project
return self_monitoring_project_not_implemented unless Feature.enabled?(:self_monitoring_project)
job_id = params[:job_id].to_s
unless job_id.length <= PARAM_JOB_ID_MAX_SIZE
......@@ -97,23 +99,66 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
end
if Gitlab::CurrentSettings.instance_administration_project_id.present?
render status: :ok, json: self_monitoring_data
return render status: :ok, json: self_monitoring_data
elsif SelfMonitoringProjectCreateWorker.in_progress?(job_id)
::Gitlab::PollingInterval.set_header(response, interval: 3_000)
render status: :accepted, json: { message: _('Job is in progress') }
return render status: :accepted, json: {
message: _('Job to create self-monitoring project is in progress')
}
end
render status: :bad_request, json: {
message: _('Self-monitoring project does not exist. Please check logs ' \
'for any error messages')
}
end
def delete_self_monitoring_project
job_id = SelfMonitoringProjectDeleteWorker.perform_async
render status: :accepted, json: {
job_id: job_id,
monitor_status: status_delete_self_monitoring_project_admin_application_settings_path
}
end
def status_delete_self_monitoring_project
job_id = params[:job_id].to_s
unless job_id.length <= PARAM_JOB_ID_MAX_SIZE
return render status: :bad_request, json: {
message: _('Parameter "job_id" cannot exceed length of %{job_id_max_size}' %
{ job_id_max_size: PARAM_JOB_ID_MAX_SIZE })
}
end
else
render status: :bad_request, json: {
message: _('Self-monitoring project does not exist. Please check logs ' \
'for any error messages')
if Gitlab::CurrentSettings.instance_administration_project_id.nil?
return render status: :ok, json: {
message: _('Self-monitoring project has been successfully deleted')
}
elsif SelfMonitoringProjectDeleteWorker.in_progress?(job_id)
::Gitlab::PollingInterval.set_header(response, interval: 3_000)
return render status: :accepted, json: {
message: _('Job to delete self-monitoring project is in progress')
}
end
render status: :bad_request, json: {
message: _('Self-monitoring project was not deleted. Please check logs ' \
'for any error messages')
}
end
private
def validate_self_monitoring_feature_flag_enabled
self_monitoring_project_not_implemented unless Feature.enabled?(:self_monitoring_project)
end
def self_monitoring_data
{
project_id: Gitlab::CurrentSettings.instance_administration_project_id,
......
......@@ -18,7 +18,7 @@ module RequiresWhitelistedMonitoringClient
# debugging purposes
return true if Rails.env.development? && request.local?
ip_whitelist.any? { |e| e.include?(Gitlab::RequestContext.client_ip) }
ip_whitelist.any? { |e| e.include?(Gitlab::RequestContext.instance.client_ip) }
end
def ip_whitelist
......
......@@ -3,6 +3,7 @@
class Groups::GroupLinksController < Groups::ApplicationController
before_action :check_feature_flag!
before_action :authorize_admin_group!
before_action :group_link, only: [:update, :destroy]
def create
shared_with_group = Group.find(params[:shared_with_group_id]) if params[:shared_with_group_id].present?
......@@ -22,12 +23,35 @@ class Groups::GroupLinksController < Groups::ApplicationController
redirect_to group_group_members_path(group)
end
def update
@group_link.update(group_link_params)
end
def destroy
Groups::GroupLinks::DestroyService.new(nil, nil).execute(@group_link)
respond_to do |format|
format.html do
redirect_to group_group_members_path(group), status: :found
end
format.js { head :ok }
end
end
private
def group_link
@group_link ||= group.shared_with_group_links.find(params[:id])
end
def group_link_create_params
params.permit(:shared_group_access, :expires_at)
end
def group_link_params
params.require(:group_link).permit(:group_access, :expires_at)
end
def check_feature_flag!
render_404 unless Feature.enabled?(:share_group_with_group)
end
......
......@@ -20,28 +20,17 @@ class Groups::GroupMembersController < Groups::ApplicationController
:override
def index
can_manage_members = can?(current_user, :admin_group_member, @group)
@sort = params[:sort].presence || sort_value_name
@project = @group.projects.find(params[:project_id]) if params[:project_id]
@members = find_members
if can_manage_members
@invited_members = @members.invite
@invited_members = @invited_members.search_invite_email(params[:search_invited]) if params[:search_invited].present?
@invited_members = present_members(@invited_members.page(params[:invited_members_page]).per(MEMBER_PER_PAGE_LIMIT))
@skip_groups = @group.related_group_ids
@invited_members = present_invited_members(@members)
end
@members = @members.non_invite
@members = @members.search(params[:search]) if params[:search].present?
@members = @members.sort_by_attribute(@sort)
if can_manage_members && params[:two_factor].present?
@members = @members.filter_by_2fa(params[:two_factor])
end
@members = @members.page(params[:page]).per(MEMBER_PER_PAGE_LIMIT)
@members = present_members(@members)
@members = present_group_members(@members)
@requesters = present_members(
AccessRequestsFinder.new(@group).execute(current_user))
......@@ -54,8 +43,30 @@ class Groups::GroupMembersController < Groups::ApplicationController
private
def present_invited_members(members)
invited_members = members.invite
if params[:search_invited].present?
invited_members = invited_members.search_invite_email(params[:search_invited])
end
present_members(invited_members
.page(params[:invited_members_page])
.per(MEMBER_PER_PAGE_LIMIT))
end
def find_members
GroupMembersFinder.new(@group).execute(include_relations: requested_relations)
filter_params = params.slice(:two_factor, :search).merge(sort: @sort)
GroupMembersFinder.new(@group, current_user).execute(include_relations: requested_relations, params: filter_params)
end
def can_manage_members
can?(current_user, :admin_group_member, @group)
end
def present_group_members(original_members)
members = original_members.page(params[:page]).per(MEMBER_PER_PAGE_LIMIT)
present_members(members)
end
end
......
# frozen_string_literal: true
class GroupMembersFinder < UnionFinder
def initialize(group)
# Params can be any of the following:
# two_factor: string. 'enabled' or 'disabled' are returning different set of data, other values are not effective.
# sort: string
# search: string
def initialize(group, user = nil)
@group = group
@user = user
end
# rubocop: disable CodeReuse/ActiveRecord
def execute(include_relations: [:inherited, :direct])
group_members = @group.members
def execute(include_relations: [:inherited, :direct], params: {})
group_members = group.members
relations = []
return group_members if include_relations == [:direct]
relations << group_members if include_relations.include?(:direct)
if include_relations.include?(:inherited) && @group.parent
if include_relations.include?(:inherited) && group.parent
parents_members = GroupMember.non_request
.where(source_id: @group.ancestors.select(:id))
.where.not(user_id: @group.users.select(:id))
.where(source_id: group.ancestors.select(:id))
.where.not(user_id: group.users.select(:id))
relations << parents_members
end
if include_relations.include?(:descendants)
descendant_members = GroupMember.non_request
.where(source_id: @group.descendants.select(:id))
.where.not(user_id: @group.users.select(:id))
.where(source_id: group.descendants.select(:id))
.where.not(user_id: group.users.select(:id))
relations << descendant_members
end
find_union(relations, GroupMember)
members = find_union(relations, GroupMember)
filter_members(members, params)
end
# rubocop: enable CodeReuse/ActiveRecord
private
attr_reader :user, :group
def filter_members(members, params)
members = members.search(params[:search]) if params[:search].present?
members = members.sort_by_attribute(params[:sort]) if params[:sort].present?
if can_manage_members && params[:two_factor].present?
members = members.filter_by_2fa(params[:two_factor])
end
members
end
def can_manage_members
Ability.allowed?(user, :admin_group_member, group)
end
end
GroupMembersFinder.prepend_if_ee('EE::GroupMembersFinder')
......@@ -42,7 +42,7 @@ module Mutations
if project_path.present?
project = find_project!(project_path: project_path)
elsif !can_create_personal_snippet?
raise_resource_not_avaiable_error!
raise_resource_not_available_error!
end
snippet = CreateSnippetService.new(project,
......
......@@ -344,6 +344,12 @@ module ApplicationSettingsHelper
'status_create_self_monitoring_project_path' =>
status_create_self_monitoring_project_admin_application_settings_path,
'delete_self_monitoring_project_path' =>
delete_self_monitoring_project_admin_application_settings_path,
'status_delete_self_monitoring_project_path' =>
status_delete_self_monitoring_project_admin_application_settings_path,
'self_monitoring_project_exists' =>
Gitlab::CurrentSettings.instance_administration_project.present?,
......
......@@ -4,6 +4,10 @@ module Groups::GroupMembersHelper
def group_member_select_options
{ multiple: true, class: 'input-clamp qa-member-select-field ', scope: :all, email_user: true }
end
def render_invite_member_for_group(group, default_access_level)
render 'shared/members/invite_member', submit_url: group_group_members_path(group), access_levels: GroupMember.access_level_roles, default_access_level: default_access_level
end
end
Groups::GroupMembersHelper.prepend_if_ee('EE::Groups::GroupMembersHelper')
......@@ -85,7 +85,8 @@ module SelectsHelper
first_user: opts[:first_user] && current_user ? current_user.username : false,
current_user: opts[:current_user] || false,
author_id: opts[:author_id] || '',
skip_users: opts[:skip_users] ? opts[:skip_users].map(&:id) : nil
skip_users: opts[:skip_users] ? opts[:skip_users].map(&:id) : nil,
qa_selector: opts[:qa_selector] || ''
}
end
end
......
......@@ -169,7 +169,11 @@ class ApplicationSetting < ApplicationRecord
validates :gitaly_timeout_default,
presence: true,
numericality: { only_integer: true, greater_than_or_equal_to: 0 }
numericality: {
only_integer: true,
greater_than_or_equal_to: 0,
less_than_or_equal_to: Settings.gitlab.max_request_duration_seconds
}
validates :gitaly_timeout_medium,
presence: true,
......
......@@ -420,6 +420,12 @@ class Group < Namespace
GroupMember.where(source_id: self_and_ancestors_ids, user_id: user.id).order(:access_level).last
end
def related_group_ids
[id,
*ancestors.pluck(:id),
*shared_with_group_links.pluck(:shared_with_group_id)]
end
def hashed_storage?(_feature)