Skip to content
Snippets Groups Projects
Commit eacea1ee authored by Laura Callahan's avatar Laura Callahan :red_circle:
Browse files

Add guest overage modal

parent e16d4f70
No related branches found
No related tags found
No related merge requests found
This commit is part of merge request !106515. Comments created here will be created in the context of that merge request.
......@@ -3,6 +3,10 @@ import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
import { mapActions } from 'vuex';
import { s__ } from '~/locale';
import {
userOverageConfirmAction,
guestOverageConfirmAction,
} from 'ee_else_ce/members/guest_overage_confirm_action';
export default {
name: 'RoleDropdown',
......@@ -50,22 +54,31 @@ export default {
return dispatch(`${this.namespace}/updateMemberRole`, payload);
},
}),
handleSelect(value, name) {
async handleSelect(value, name) {
if (value === this.member.accessLevel.integerValue) {
return;
}
this.busy = true;
const confirmed = await guestOverageConfirmAction({
dropdownIntValue: value,
subscriptionSeats: 0,
totalUsers: 0,
groupName: __('a name'),
});
if (!confirmed) {
return;
}
this.updateMemberRole({
memberId: this.member.id,
accessLevel: { integerValue: value, stringValue: name },
})
.then(() => {
this.$toast.show(s__('Members|Role updated successfully.'));
this.busy = false;
})
.catch(() => {
.finally(() => {
this.busy = false;
});
},
......
export const guestOverageConfirmAction = () => {
return true;
};
......@@ -417,6 +417,26 @@ CREATE TABLE batched_background_migration_job_transition_logs (
)
PARTITION BY RANGE (created_at);
 
CREATE TABLE p_ci_builds_metadata (
project_id integer NOT NULL,
timeout integer,
timeout_source integer DEFAULT 1 NOT NULL,
interruptible boolean,
config_options jsonb,
config_variables jsonb,
has_exposed_artifacts boolean,
environment_auto_stop_in character varying(255),
expanded_environment_name character varying(255),
secrets jsonb DEFAULT '{}'::jsonb NOT NULL,
build_id bigint NOT NULL,
id bigint NOT NULL,
runtime_runner_features jsonb DEFAULT '{}'::jsonb NOT NULL,
id_tokens jsonb DEFAULT '{}'::jsonb NOT NULL,
partition_id bigint DEFAULT 100 NOT NULL,
debug_trace_enabled boolean DEFAULT false NOT NULL
)
PARTITION BY LIST (partition_id);
CREATE TABLE incident_management_pending_alert_escalations (
id bigint NOT NULL,
rule_id bigint NOT NULL,
......@@ -11448,10 +11468,6 @@ CREATE TABLE application_settings (
database_grafana_api_url text,
database_grafana_tag text,
public_runner_releases_url text DEFAULT 'https://gitlab.com/api/v4/projects/gitlab-org%2Fgitlab-runner/releases'::text NOT NULL,
password_uppercase_required boolean DEFAULT false NOT NULL,
password_lowercase_required boolean DEFAULT false NOT NULL,
password_number_required boolean DEFAULT false NOT NULL,
password_symbol_required boolean DEFAULT false NOT NULL,
encrypted_arkose_labs_public_api_key bytea,
encrypted_arkose_labs_public_api_key_iv bytea,
encrypted_arkose_labs_private_api_key bytea,
......@@ -11462,7 +11478,6 @@ CREATE TABLE application_settings (
inactive_projects_min_size_mb integer DEFAULT 0 NOT NULL,
inactive_projects_send_warning_email_after_months integer DEFAULT 1 NOT NULL,
delayed_group_deletion boolean DEFAULT true NOT NULL,
maven_package_requests_forwarding boolean DEFAULT true NOT NULL,
arkose_labs_namespace text DEFAULT 'client'::text NOT NULL,
max_export_size integer DEFAULT 0,
encrypted_slack_app_signing_secret bytea,
......@@ -11477,8 +11492,12 @@ CREATE TABLE application_settings (
encrypted_dingtalk_app_key_iv bytea,
encrypted_dingtalk_app_secret bytea,
encrypted_dingtalk_app_secret_iv bytea,
jira_connect_application_key text,
globally_allowed_ips text DEFAULT ''::text NOT NULL,
password_uppercase_required boolean DEFAULT false NOT NULL,
password_lowercase_required boolean DEFAULT false NOT NULL,
password_number_required boolean DEFAULT false NOT NULL,
password_symbol_required boolean DEFAULT false NOT NULL,
jira_connect_application_key text,
container_registry_pre_import_tags_rate numeric(6,2) DEFAULT 0.5 NOT NULL,
license_usage_data_exported boolean DEFAULT false NOT NULL,
phone_verification_code_enabled boolean DEFAULT false NOT NULL,
......@@ -11489,38 +11508,39 @@ CREATE TABLE application_settings (
encrypted_feishu_app_key_iv bytea,
encrypted_feishu_app_secret bytea,
encrypted_feishu_app_secret_iv bytea,
git_rate_limit_users_allowlist text[] DEFAULT '{}'::text[] NOT NULL,
error_tracking_enabled boolean DEFAULT false NOT NULL,
error_tracking_api_url text,
git_rate_limit_users_allowlist text[] DEFAULT '{}'::text[] NOT NULL,
error_tracking_access_token_encrypted text,
invitation_flow_enforcement boolean DEFAULT false NOT NULL,
package_registry_cleanup_policies_worker_capacity integer DEFAULT 2 NOT NULL,
deactivate_dormant_users_period integer DEFAULT 90 NOT NULL,
auto_ban_user_on_excessive_projects_download boolean DEFAULT false NOT NULL,
invitation_flow_enforcement boolean DEFAULT false NOT NULL,
maven_package_requests_forwarding boolean DEFAULT true NOT NULL,
max_pages_custom_domains_per_project integer DEFAULT 0 NOT NULL,
cube_api_base_url text,
encrypted_cube_api_key bytea,
encrypted_cube_api_key_iv bytea,
dashboard_limit_enabled boolean DEFAULT false NOT NULL,
dashboard_limit integer DEFAULT 0 NOT NULL,
dashboard_notification_limit integer DEFAULT 0 NOT NULL,
dashboard_enforcement_limit integer DEFAULT 0 NOT NULL,
dashboard_limit_new_namespace_creation_enforcement_date date,
jitsu_host text,
jitsu_project_xid text,
clickhouse_connection_string text,
jitsu_administrator_email text,
encrypted_jitsu_administrator_password bytea,
encrypted_jitsu_administrator_password_iv bytea,
dashboard_limit_enabled boolean DEFAULT false NOT NULL,
dashboard_limit integer DEFAULT 0 NOT NULL,
dashboard_notification_limit integer DEFAULT 0 NOT NULL,
dashboard_enforcement_limit integer DEFAULT 0 NOT NULL,
dashboard_limit_new_namespace_creation_enforcement_date date,
can_create_group boolean DEFAULT true NOT NULL,
lock_maven_package_requests_forwarding boolean DEFAULT false NOT NULL,
lock_pypi_package_requests_forwarding boolean DEFAULT false NOT NULL,
lock_npm_package_requests_forwarding boolean DEFAULT false NOT NULL,
jira_connect_proxy_url text,
password_expiration_enabled boolean DEFAULT false NOT NULL,
password_expires_in_days integer DEFAULT 90 NOT NULL,
password_expires_notice_before_days integer DEFAULT 7 NOT NULL,
product_analytics_enabled boolean DEFAULT false NOT NULL,
jira_connect_proxy_url text,
email_confirmation_setting smallint DEFAULT 0,
disable_admin_oauth_scopes boolean DEFAULT false NOT NULL,
default_preferred_language text DEFAULT 'en'::text NOT NULL,
......@@ -12714,26 +12734,6 @@ CREATE SEQUENCE ci_builds_id_seq
 
ALTER SEQUENCE ci_builds_id_seq OWNED BY ci_builds.id;
 
CREATE TABLE p_ci_builds_metadata (
project_id integer NOT NULL,
timeout integer,
timeout_source integer DEFAULT 1 NOT NULL,
interruptible boolean,
config_options jsonb,
config_variables jsonb,
has_exposed_artifacts boolean,
environment_auto_stop_in character varying(255),
expanded_environment_name character varying(255),
secrets jsonb DEFAULT '{}'::jsonb NOT NULL,
build_id bigint NOT NULL,
id bigint NOT NULL,
runtime_runner_features jsonb DEFAULT '{}'::jsonb NOT NULL,
id_tokens jsonb DEFAULT '{}'::jsonb NOT NULL,
partition_id bigint DEFAULT 100 NOT NULL,
debug_trace_enabled boolean DEFAULT false NOT NULL
)
PARTITION BY LIST (partition_id);
CREATE SEQUENCE ci_builds_metadata_id_seq
START WITH 1
INCREMENT BY 1
......@@ -18140,13 +18140,13 @@ CREATE TABLE namespace_settings (
runner_token_expiration_interval integer,
subgroup_runner_token_expiration_interval integer,
project_runner_token_expiration_interval integer,
show_diff_preview_in_email boolean DEFAULT true NOT NULL,
enabled_git_access_protocol smallint DEFAULT 0 NOT NULL,
unique_project_download_limit smallint DEFAULT 0 NOT NULL,
unique_project_download_limit_interval_in_seconds integer DEFAULT 0 NOT NULL,
project_import_level smallint DEFAULT 50 NOT NULL,
unique_project_download_limit_allowlist text[] DEFAULT '{}'::text[] NOT NULL,
auto_ban_user_on_excessive_projects_download boolean DEFAULT false NOT NULL,
show_diff_preview_in_email boolean DEFAULT true NOT NULL,
only_allow_merge_if_pipeline_succeeds boolean DEFAULT false NOT NULL,
allow_merge_on_skipped_pipeline boolean DEFAULT false NOT NULL,
only_allow_merge_if_all_discussions_are_resolved boolean DEFAULT false NOT NULL,
......@@ -20376,11 +20376,11 @@ CREATE TABLE project_settings (
target_platforms character varying[] DEFAULT '{}'::character varying[] NOT NULL,
enforce_auth_checks_on_uploads boolean DEFAULT true NOT NULL,
selective_code_owner_removals boolean DEFAULT false NOT NULL,
issue_branch_template text,
show_diff_preview_in_email boolean DEFAULT true NOT NULL,
jitsu_key text,
suggested_reviewers_enabled boolean DEFAULT false NOT NULL,
jitsu_key text,
only_allow_merge_if_all_status_checks_passed boolean DEFAULT false NOT NULL,
issue_branch_template text,
mirror_branch_regex text,
allow_pipeline_trigger_approve_deployment boolean DEFAULT false NOT NULL,
CONSTRAINT check_2981f15877 CHECK ((char_length(jitsu_key) <= 100)),
......@@ -28537,10 +28537,6 @@ CREATE UNIQUE INDEX p_ci_builds_metadata_build_id_partition_id_idx ON ONLY p_ci_
 
CREATE UNIQUE INDEX index_ci_builds_metadata_on_build_id_partition_id_unique ON ci_builds_metadata USING btree (build_id, partition_id);
 
CREATE UNIQUE INDEX p_ci_builds_metadata_id_partition_id_idx ON ONLY p_ci_builds_metadata USING btree (id, partition_id);
CREATE UNIQUE INDEX index_ci_builds_metadata_on_id_partition_id_unique ON ci_builds_metadata USING btree (id, partition_id);
CREATE INDEX p_ci_builds_metadata_project_id_idx ON ONLY p_ci_builds_metadata USING btree (project_id);
 
CREATE INDEX index_ci_builds_metadata_on_project_id ON ci_builds_metadata USING btree (project_id);
......@@ -32841,8 +32837,6 @@ ALTER INDEX p_ci_builds_metadata_build_id_id_idx ATTACH PARTITION index_ci_build
 
ALTER INDEX p_ci_builds_metadata_build_id_partition_id_idx ATTACH PARTITION index_ci_builds_metadata_on_build_id_partition_id_unique;
 
ALTER INDEX p_ci_builds_metadata_id_partition_id_idx ATTACH PARTITION index_ci_builds_metadata_on_id_partition_id_unique;
ALTER INDEX p_ci_builds_metadata_project_id_idx ATTACH PARTITION index_ci_builds_metadata_on_project_id;
 
CREATE TRIGGER chat_names_loose_fk_trigger AFTER DELETE ON chat_names REFERENCING OLD TABLE AS old_table FOR EACH STATEMENT EXECUTE FUNCTION insert_into_loose_foreign_keys_deleted_records();
import { __, s__, n__, sprintf } from '~/locale';
import { helpPagePath } from '~/helpers/help_page_helper';
export const overageModalFields = Object.freeze({
OVERAGE_MODAL_LINK: helpPagePath('subscriptions/quarterly_reconciliation'),
OVERAGE_MODAL_TITLE: s__('MembersOverage|You are about to incur additional charges'),
OVERAGE_MODAL_BACK_BUTTON: __('Back'),
OVERAGE_MODAL_CONTINUE_BUTTON: __('Continue'),
OVERAGE_MODAL_LINK_TEXT: __('Learn more.'),
});
export const OVERAGE_MODAL_LINK = helpPagePath('subscriptions/quarterly_reconciliation');
export const OVERAGE_MODAL_TITLE = s__('MembersOverage|You are about to incur additional charges');
......
import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal';
import {
overageModalFields,
overageModalInfoText,
overageModalInfoWarning,
} from '../invite_members/constants';
const shouldShowOverageModal = (dropdownIntValue) => {
return this.glFeatures.showOverageOnRolePromotion && dropdownIntValue === 10;
};
const getConfirmContent = (subscriptionSeats, totalUsers, groupName) => {
const infoText = overageModalInfoText(subscriptionSeats);
const infoWarning = overageModalInfoWarning(totalUsers, groupName);
return `${infoText} ${infoWarning}`;
};
export const guestOverageConfirmAction = async ({
dropdownIntValue,
subscriptionSeats,
totalUsers,
groupName,
}) => {
if (!shouldShowOverageModal(dropdownIntValue)) {
return true;
}
const confirmContent = getConfirmContent(subscriptionSeats, totalUsers, groupName);
return confirmAction(confirmContent, {
primaryBtnText: overageModalFields.OVERAGE_MODAL_CONTINUE_BUTTON,
cancelBtnText: overageModalFields.OVERAGE_MODAL_BACK_BUTTON,
});
};
......@@ -8,6 +8,12 @@ module ProjectMembersController
private
prepended do
before_action do
push_frontend_feature_flag(:show_overage_on_role_promotion)
end
end
override :invited_members
def invited_members
super.or(members.awaiting.with_invited_user_state)
......
---
name: show_overage_on_role_promotion
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/89180/
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/199893
milestone: '15.2'
type: development
group: group::billing and subscription management
default_enabled: false
......@@ -5,9 +5,9 @@
RSpec.describe 'Projects > Audit Events', :js, feature_category: :audit_events do
include Spec::Support::Helpers::Features::MembersHelpers
let_it_be(:user) { create(:user) }
let_it_be(:pete) { create(:user, name: 'Pete') }
let_it_be_with_reload(:project) { create(:project, :repository, namespace: user.namespace) }
let(:user) { create(:user) }
let(:pete) { create(:user, name: 'Pete') }
let(:project) { create(:project, :repository, namespace: user.namespace) }
before do
project.add_maintainer(user)
......@@ -110,27 +110,60 @@
end
describe 'changing a user access level' do
before do
project.add_developer(pete)
end
describe 'without show_overage_on_role_promotion feature flag' do
before do
stub_feature_flags(show_overage_on_role_promotion: false)
project.add_developer(pete)
end
it "appears in the project's audit events" do
visit project_project_members_path(project)
it "appears in the project's audit events" do
visit project_project_members_path(project)
page.within find_member_row(pete) do
click_button 'Developer'
click_button 'Maintainer'
end
page.within find_member_row(pete) do
click_button 'Developer'
click_button 'Maintainer'
page.within('.sidebar-top-level-items') do
find(:link, text: 'Security & Compliance').click
click_link 'Audit events'
end
page.within('.audit-log-table') do
expect(page).to have_content 'Changed access level from Developer to Maintainer'
expect(page).to have_content(project.first_owner.name)
expect(page).to have_content('Pete')
end
end
end
page.within('.sidebar-top-level-items') do
find(:link, text: 'Security & Compliance').click
click_link 'Audit events'
describe 'with show_overage_on_role_promotion feature flag' do
before do
project.add_developer(pete)
end
page.within('.audit-log-table') do
expect(page).to have_content 'Changed access level from Developer to Maintainer'
expect(page).to have_content(project.first_owner.name)
expect(page).to have_content('Pete')
it "appears in the project's audit events" do
visit project_project_members_path(project)
page.within find_member_row(pete) do
click_button 'Developer'
click_button 'Maintainer'
end
page.within('.modal') do
click_button 'Back'
end
page.within('.sidebar-top-level-items') do
find(:link, text: 'Security & Compliance').click
click_link 'Audit events'
end
page.within('.audit-log-table') do
expect(page).to have_content 'Changed access level from Developer to Maintainer'
expect(page).to have_content(project.first_owner.name)
expect(page).to have_content('Pete')
end
end
end
end
......@@ -142,7 +175,7 @@
end
it "appears in the project's audit events", :js do
visit project_settings_merge_requests_path(project)
visit edit_project_path(project)
page.within('[data-testid="merge-request-approval-settings"]') do
find('[data-testid="prevent-author-approval"] > input').set(false)
......@@ -171,10 +204,20 @@
end
end
describe 'filter by date' do
let!(:audit_event_1) { create(:project_audit_event, entity_type: 'Project', entity_id: project.id, created_at: 5.days.ago) }
let!(:audit_event_2) { create(:project_audit_event, entity_type: 'Project', entity_id: project.id, created_at: 3.days.ago) }
let!(:audit_event_3) { create(:project_audit_event, entity_type: 'Project', entity_id: project.id, created_at: Date.current) }
let!(:events_path) { :project_audit_events_path }
let!(:entity) { project }
it_behaves_like 'audit events date filter'
end
describe 'combined list of authenticated and unauthenticated users' do
let_it_be(:audit_event_1) { create(:project_audit_event, :unauthenticated, entity_type: 'Project', entity_id: project.id) }
let_it_be(:audit_event_2) { create(:project_audit_event, author_id: non_existing_record_id, entity_type: 'Project', entity_id: project.id) }
let_it_be(:audit_event_3) { create(:project_audit_event, entity_type: 'Project', entity_id: project.id) }
let!(:audit_event_1) { create(:project_audit_event, :unauthenticated, entity_type: 'Project', entity_id: project.id) }
let!(:audit_event_2) { create(:project_audit_event, author_id: non_existing_record_id, entity_type: 'Project', entity_id: project.id) }
let!(:audit_event_3) { create(:project_audit_event, entity_type: 'Project', entity_id: project.id) }
it 'displays the correct authors names' do
visit project_audit_events_path(project)
......@@ -188,32 +231,4 @@
end
end
end
describe 'audit event filter' do
let_it_be(:events_path) { :project_audit_events_path }
let_it_be(:entity) { project }
describe 'filter by date' do
let_it_be(:audit_event_1) { create(:project_audit_event, entity_type: 'Project', entity_id: project.id, created_at: 5.days.ago) }
let_it_be(:audit_event_2) { create(:project_audit_event, entity_type: 'Project', entity_id: project.id, created_at: 3.days.ago) }
let_it_be(:audit_event_3) { create(:project_audit_event, entity_type: 'Project', entity_id: project.id, created_at: Date.current) }
it_behaves_like 'audit events date filter'
end
context 'signed in as a developer' do
before do
project.add_developer(pete)
sign_in(pete)
end
describe 'filter by author' do
let_it_be(:audit_event_1) { create(:project_audit_event, entity_type: 'Project', entity_id: project.id, created_at: Date.today, ip_address: '1.1.1.1', author_id: pete.id) }
let_it_be(:audit_event_2) { create(:project_audit_event, entity_type: 'Project', entity_id: project.id, created_at: Date.today, ip_address: '0.0.0.0', author_id: user.id) }
let_it_be(:author) { user }
it_behaves_like 'audit events author filtering without entity admin permission'
end
end
end
end
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment