Skip to content
Snippets Groups Projects
Commit 4a78cb64 authored by Raimund Hook's avatar Raimund Hook :m:
Browse files

Merge branch 'stingrayza/add-mastodon' into 'master'

Add support for Mastodon as user social

See merge request gitlab-org/gitlab!132892



Merged-by: default avatarRaimund Hook <rhook@gitlab.com>
Approved-by: Lorenz van Herwaarden's avatarLorenz van Herwaarden <lvanherwaarden@gitlab.com>
Approved-by: default avatarSerena Fang <sfang@gitlab.com>
Approved-by: default avatarGreg Alfaro <galfaro@gitlab.com>
Approved-by: Adam Hegyi's avatarAdam Hegyi <ahegyi@gitlab.com>
Reviewed-by: default avatarRaimund Hook <rhook@gitlab.com>
Reviewed-by: Adam Hegyi's avatarAdam Hegyi <ahegyi@gitlab.com>
Reviewed-by: default avatarDmitry Gruzd <dgruzd@gitlab.com>
Reviewed-by: Niklas van Schrick's avatarNiklas <mc.taucher2003@gmail.com>
Reviewed-by: default avatarMehmet Emin INAC <minac@gitlab.com>
Co-authored-by: default avatarJon Glassman <jglassman@gitlab.com>
Co-authored-by: Niklas van Schrick's avatarNiklas <mc.taucher2003@gmail.com>
parents b6a59a37 fb2df5c6
No related branches found
No related tags found
No related merge requests found
Showing
with 137 additions and 3 deletions
......@@ -655,6 +655,7 @@ $status-icon-size: 22px;
*/
$discord: #5865f2;
$linkedin: #2867b2;
$mastodon: #6364ff;
$skype: #0078d7;
$twitter: #1d9bf0;
......
......@@ -242,6 +242,10 @@
color: $discord;
}
.mastodon-icon {
color: $mastodon;
}
.key-created-at {
line-height: 42px;
}
......
......@@ -342,6 +342,7 @@ def allowed_user_params
:bio,
:can_create_group,
:color_scheme_id,
:discord,
:email,
:extern_uid,
:external,
......@@ -350,6 +351,7 @@ def allowed_user_params
:hide_no_ssh_key,
:key_id,
:linkedin,
:mastodon,
:name,
:password_expires_at,
:projects_limit,
......@@ -358,7 +360,6 @@ def allowed_user_params
:skype,
:theme_id,
:twitter,
:discord,
:username,
:website_url,
:note,
......
......@@ -111,6 +111,7 @@ def user_params_attributes
[
:avatar,
:bio,
:discord,
:email,
:role,
:gitpod_enabled,
......@@ -119,12 +120,12 @@ def user_params_attributes
:hide_project_limit,
:linkedin,
:location,
:mastodon,
:name,
:public_email,
:commit_email,
:skype,
:twitter,
:discord,
:username,
:website_url,
:organization,
......
......@@ -371,6 +371,14 @@ def discord_url(user)
"https://discord.com/users/#{user.discord}"
end
def mastodon_url(user)
return '' if user.mastodon.blank?
url = user.mastodon.match UserDetail::MASTODON_VALIDATION_REGEX
"https://#{url[2]}/@#{url[1]}"
end
def collapsed_sidebar?
cookies["sidebar_collapsed"] == "true"
end
......
......@@ -417,6 +417,7 @@ def update_tracked_fields!(request)
delegate :pronouns, :pronouns=, to: :user_detail, allow_nil: true
delegate :pronunciation, :pronunciation=, to: :user_detail, allow_nil: true
delegate :registration_objective, :registration_objective=, to: :user_detail, allow_nil: true
delegate :mastodon, :mastodon=, to: :user_detail, allow_nil: true
delegate :linkedin, :linkedin=, to: :user_detail, allow_nil: true
delegate :twitter, :twitter=, to: :user_detail, allow_nil: true
delegate :skype, :skype=, to: :user_detail, allow_nil: true
......
......@@ -17,10 +17,24 @@ class UserDetail < MainClusterwide::ApplicationRecord
DEFAULT_FIELD_LENGTH = 500
MASTODON_VALIDATION_REGEX = /
\A # beginning of string
@?\b # optional leading at
([\w\d.%+-]+) # character group to pick up words in user portion of username
@ # separator between user and host
( # beginning of charagter group for host portion
[\w\d.-]+ # character group to pick up words in host portion of username
\.\w{2,} # pick up tld of host domain, 2 chars or more
)\b # end of character group to pick up words in host portion of username
\z # end of string
/x
validates :discord, length: { maximum: DEFAULT_FIELD_LENGTH }, allow_blank: true
validate :discord_format
validates :linkedin, length: { maximum: DEFAULT_FIELD_LENGTH }, allow_blank: true
validates :location, length: { maximum: DEFAULT_FIELD_LENGTH }, allow_blank: true
validates :mastodon, length: { maximum: DEFAULT_FIELD_LENGTH }, allow_blank: true
validate :mastodon_format
validates :organization, length: { maximum: DEFAULT_FIELD_LENGTH }, allow_blank: true
validates :skype, length: { maximum: DEFAULT_FIELD_LENGTH }, allow_blank: true
validates :twitter, length: { maximum: DEFAULT_FIELD_LENGTH }, allow_blank: true
......@@ -32,7 +46,7 @@ class UserDetail < MainClusterwide::ApplicationRecord
enum registration_objective: REGISTRATION_OBJECTIVE_PAIRS, _suffix: true
def sanitize_attrs
%i[discord linkedin skype twitter website_url].each do |attr|
%i[discord linkedin mastodon skype twitter website_url].each do |attr|
value = self[attr]
self[attr] = Sanitize.clean(value) if value.present?
end
......@@ -49,6 +63,7 @@ def prevent_nil_fields
self.discord = '' if discord.nil?
self.linkedin = '' if linkedin.nil?
self.location = '' if location.nil?
self.mastodon = '' if mastodon.nil?
self.organization = '' if organization.nil?
self.skype = '' if skype.nil?
self.twitter = '' if twitter.nil?
......@@ -62,4 +77,10 @@ def discord_format
errors.add(:discord, _('must contain only a discord user ID.'))
end
def mastodon_format
return if mastodon.blank? || mastodon =~ UserDetail::MASTODON_VALIDATION_REGEX
errors.add(:mastodon, _('must contain only a mastodon username.'))
end
UserDetail.prepend_mod_with('UserDetail')
......@@ -122,6 +122,10 @@
allow_empty: true}
%small.form-text.text-gl-muted
= external_accounts_docs_link
- if Feature.enabled?(:mastodon_social_ui, @user)
.form-group.gl-form-group
= f.label :mastodon
= f.text_field :mastodon, class: 'gl-form-input form-control gl-md-form-input-lg', placeholder: "@robin@example.com"
.form-group.gl-form-group
= f.label :website_url, s_('Profiles|Website url')
......
......@@ -111,6 +111,10 @@
= render 'middle_dot_divider', breakpoint: 'sm' do
= link_to discord_url(@user), class: 'gl-hover-text-decoration-none', title: "Discord", target: '_blank', rel: 'noopener noreferrer nofollow' do
= sprite_icon('discord', css_class: 'discord-icon')
- if Feature.enabled?(:mastodon_social_ui, @user) && @user.mastodon.present?
= render 'middle_dot_divider', breakpoint: 'sm' do
= link_to mastodon_url(@user), class: 'gl-hover-text-decoration-none', title: "Mastodon", target: '_blank', rel: 'noopener noreferrer nofollow' do
= sprite_icon('mastodon', css_class: 'mastodon-icon')
- if @user.website_url.present?
= render 'middle_dot_divider', stacking: true do
- if Feature.enabled?(:security_auto_fix) && @user.bot?
......
---
name: mastodon_social_ui
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/132892
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/428163
milestone: '16.5'
type: development
group: group::tenant scale
default_enabled: false
# frozen_string_literal: true
class AddMastodonToUserDetails < Gitlab::Database::Migration[2.1]
disable_ddl_transaction!
USER_DETAILS_FIELD_LIMIT = 500
def up
with_lock_retries do
add_column :user_details, :mastodon, :text, default: '', null: false, if_not_exists: true
end
add_text_limit :user_details, :mastodon, USER_DETAILS_FIELD_LIMIT
end
def down
with_lock_retries do
remove_column :user_details, :mastodon
end
end
end
652375e6b7318fe85b4b23eac3cce88618136341cee7721522adacbe52a52c66
\ No newline at end of file
......@@ -24088,6 +24088,7 @@ CREATE TABLE user_details (
enterprise_group_id bigint,
enterprise_group_associated_at timestamp with time zone,
email_reset_offered_at timestamp with time zone,
mastodon text DEFAULT ''::text NOT NULL,
CONSTRAINT check_245664af82 CHECK ((char_length(webauthn_xid) <= 100)),
CONSTRAINT check_444573ee52 CHECK ((char_length(skype) <= 500)),
CONSTRAINT check_466a25be35 CHECK ((char_length(twitter) <= 500)),
......@@ -24099,6 +24100,7 @@ CREATE TABLE user_details (
CONSTRAINT check_8a7fcf8a60 CHECK ((char_length(location) <= 500)),
CONSTRAINT check_a73b398c60 CHECK ((char_length(phone) <= 50)),
CONSTRAINT check_eeeaf8d4f0 CHECK ((char_length(pronouns) <= 50)),
CONSTRAINT check_f1a8a05b9a CHECK ((char_length(mastodon) <= 500)),
CONSTRAINT check_f932ed37db CHECK ((char_length(pronunciation) <= 255))
);
 
......@@ -128,6 +128,8 @@ to match your username.
## Add external accounts to your user profile page
> Mastodon user account [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/132892) as a beta feature in 16.5 [with a flag](../feature_flags.md) named `mastodon_social_ui`. Disabled by default.
You can add links to certain other external accounts you might have, like Skype and Twitter.
They can help other users connect with you on other platforms.
......@@ -138,6 +140,7 @@ To add links to other accounts:
1. In the **Main settings** section, add your:
- Discord [user ID](https://support.discord.com/hc/en-us/articles/206346498-Where-can-I-find-my-User-Server-Message-ID-).
- LinkedIn profile name.
- Mastodon username.
- Skype username.
- Twitter @username.
......
......@@ -57148,6 +57148,9 @@ msgstr ""
msgid "must contain only a discord user ID."
msgstr ""
 
msgid "must contain only a mastodon username."
msgstr ""
msgid "must have a repository"
msgstr ""
 
......@@ -128,6 +128,16 @@
expect(user.reload.discord).to eq(discord_user_id)
expect(response).to have_gitlab_http_status(:found)
end
it 'allows updating user specified mastodon username', :aggregate_failures do
mastodon_username = '@robin@example.com'
sign_in(user)
put :update, params: { user: { mastodon: mastodon_username } }
expect(user.reload.mastodon).to eq(mastodon_username)
expect(response).to have_gitlab_http_status(:found)
end
end
describe 'GET audit_log' do
......
......@@ -637,6 +637,21 @@ def stub_controller_method(method_name, value)
expect(discord).to eq('https://discord.com/users/1234567890123456789')
end
end
context 'when mastodon is set' do
let_it_be(:user) { build(:user) }
let(:mastodon) { mastodon_url(user) }
it 'returns an empty string if mastodon username is not set' do
expect(mastodon).to eq('')
end
it 'returns mastodon url when mastodon username is set' do
user.mastodon = '@robin@example.com'
expect(mastodon).to eq('https://example.com/@robin')
end
end
end
describe '#gitlab_ui_form_for' do
......
......@@ -59,6 +59,27 @@
end
end
describe '#mastodon' do
it { is_expected.to validate_length_of(:mastodon).is_at_most(500) }
context 'when mastodon is set' do
let_it_be(:user_detail) { create(:user_detail) }
it 'accepts a valid mastodon username' do
user_detail.mastodon = '@robin@example.com'
expect(user_detail).to be_valid
end
it 'throws an error when mastodon username format is wrong' do
user_detail.mastodon = '@robin'
expect(user_detail).not_to be_valid
expect(user_detail.errors.full_messages).to match_array([_('Mastodon must contain only a mastodon username.')])
end
end
end
describe '#location' do
it { is_expected.to validate_length_of(:location).is_at_most(500) }
end
......@@ -97,6 +118,7 @@
discord: '1234567890123456789',
linkedin: 'linkedin',
location: 'location',
mastodon: '@robin@example.com',
organization: 'organization',
skype: 'skype',
twitter: 'twitter',
......@@ -117,6 +139,7 @@
it_behaves_like 'prevents `nil` value', :discord
it_behaves_like 'prevents `nil` value', :linkedin
it_behaves_like 'prevents `nil` value', :location
it_behaves_like 'prevents `nil` value', :mastodon
it_behaves_like 'prevents `nil` value', :organization
it_behaves_like 'prevents `nil` value', :skype
it_behaves_like 'prevents `nil` value', :twitter
......
......@@ -113,6 +113,9 @@
it { is_expected.to delegate_method(:linkedin).to(:user_detail).allow_nil }
it { is_expected.to delegate_method(:linkedin=).to(:user_detail).with_arguments(:args).allow_nil }
it { is_expected.to delegate_method(:mastodon).to(:user_detail).allow_nil }
it { is_expected.to delegate_method(:mastodon=).to(:user_detail).with_arguments(:args).allow_nil }
it { is_expected.to delegate_method(:twitter).to(:user_detail).allow_nil }
it { is_expected.to delegate_method(:twitter=).to(:user_detail).with_arguments(:args).allow_nil }
......
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