diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-03-18 00:10:36 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-03-18 00:10:36 +0000 |
commit | 4eee0fe3f55ecdd5d607202d93508259239b590f (patch) | |
tree | 87bfbb30e6e98ae4135221d562515888e919ec10 /app | |
parent | 0ad8135c1feeefa23ec883e409fb65b8b52882a1 (diff) | |
download | gitlab-ce-4eee0fe3f55ecdd5d607202d93508259239b590f.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
22 files changed, 272 insertions, 61 deletions
diff --git a/app/assets/javascripts/filtered_search/add_extra_tokens_for_merge_requests.js b/app/assets/javascripts/filtered_search/add_extra_tokens_for_merge_requests.js index 397ba879866..50fca995c81 100644 --- a/app/assets/javascripts/filtered_search/add_extra_tokens_for_merge_requests.js +++ b/app/assets/javascripts/filtered_search/add_extra_tokens_for_merge_requests.js @@ -72,6 +72,38 @@ export default (IssuableTokenKeys, disableTargetBranchFilter = false) => { IssuableTokenKeys.tokenKeysWithAlternative.push(targetBranchToken); } + const approvedToken = { + token: { + formattedKey: __('Approved'), + key: 'approved', + type: 'string', + param: '', + symbol: '', + icon: 'approval', + tag: __('Yes or No'), + lowercaseValueOnSubmit: true, + capitalizeTokenValue: true, + hideNotEqual: true, + }, + conditions: [ + { + url: 'approved=yes', + tokenKey: 'approved', + value: __('Yes'), + operator: '=', + }, + { + url: 'approved=no', + tokenKey: 'approved', + value: __('No'), + operator: '=', + }, + ], + }; + + IssuableTokenKeys.tokenKeys.splice(3, 0, approvedToken.token); + IssuableTokenKeys.conditions.push(...approvedToken.conditions); + const approvedBy = { token: { formattedKey: TOKEN_TITLE_APPROVED_BY, @@ -117,8 +149,8 @@ export default (IssuableTokenKeys, disableTargetBranchFilter = false) => { ], }; - const tokenPosition = 3; - IssuableTokenKeys.tokenKeys.splice(tokenPosition, 0, ...[approvedBy.token]); + const tokenPosition = 4; + IssuableTokenKeys.tokenKeys.splice(tokenPosition, 0, approvedBy.token); IssuableTokenKeys.tokenKeysWithAlternative.splice( tokenPosition, 0, diff --git a/app/assets/javascripts/filtered_search/available_dropdown_mappings.js b/app/assets/javascripts/filtered_search/available_dropdown_mappings.js index 1f8baa470d8..892e9130fe8 100644 --- a/app/assets/javascripts/filtered_search/available_dropdown_mappings.js +++ b/app/assets/javascripts/filtered_search/available_dropdown_mappings.js @@ -138,6 +138,11 @@ export default class AvailableDropdownMappings { gl: DropdownNonUser, element: this.container.querySelector('#js-dropdown-wip'), }, + approved: { + reference: null, + gl: DropdownNonUser, + element: this.container.querySelector('#js-dropdown-approved'), + }, [TOKEN_TYPE_CONFIDENTIAL]: { reference: null, gl: DropdownNonUser, diff --git a/app/assets/javascripts/pages/users/show/index.js b/app/assets/javascripts/pages/users/show/index.js index dd47baf06ec..c213753257d 100644 --- a/app/assets/javascripts/pages/users/show/index.js +++ b/app/assets/javascripts/pages/users/show/index.js @@ -1,16 +1,7 @@ -import { s__ } from '~/locale'; -import { createAlert } from '~/alert'; +import { initProfileTabs, initUserAchievements } from '~/profile'; -if (window.gon.features?.profileTabsVue) { - import('~/profile') - .then(({ initProfileTabs }) => { - initProfileTabs(); - }) - .catch(() => { - createAlert({ - message: s__( - 'UserProfile|An error occurred loading the profile. Please refresh the page to try again.', - ), - }); - }); +if (gon.features?.profileTabsVue) { + initProfileTabs(); } + +initUserAchievements(); diff --git a/app/assets/javascripts/profile/components/graphql/get_user_achievements.query.graphql b/app/assets/javascripts/profile/components/graphql/get_user_achievements.query.graphql new file mode 100644 index 00000000000..e60f383ad1c --- /dev/null +++ b/app/assets/javascripts/profile/components/graphql/get_user_achievements.query.graphql @@ -0,0 +1,21 @@ +query getUserAchievements($id: UserID!) { + user(id: $id) { + id + userAchievements { + nodes { + id + createdAt + achievement { + id + name + description + avatarUrl + namespace { + id + fullPath + } + } + } + } + } +} diff --git a/app/assets/javascripts/profile/components/user_achievements.vue b/app/assets/javascripts/profile/components/user_achievements.vue new file mode 100644 index 00000000000..790b0e9f303 --- /dev/null +++ b/app/assets/javascripts/profile/components/user_achievements.vue @@ -0,0 +1,100 @@ +<script> +import { GlPopover, GlSprintf } from '@gitlab/ui'; +import { convertToGraphQLId, getIdFromGraphQLId } from '~/graphql_shared/utils'; +import { s__ } from '~/locale'; +import { TYPENAME_USER } from '~/graphql_shared/constants'; +import timeagoMixin from '~/vue_shared/mixins/timeago'; +import getUserAchievements from './graphql/get_user_achievements.query.graphql'; + +export default { + name: 'UserAchievements', + components: { GlPopover, GlSprintf }, + mixins: [timeagoMixin], + inject: ['rootUrl', 'userId'], + apollo: { + userAchievements: { + query: getUserAchievements, + variables() { + return { + id: convertToGraphQLId(TYPENAME_USER, this.userId), + }; + }, + update(data) { + return this.processNodes(data.user.userAchievements.nodes); + }, + error() { + return []; + }, + }, + }, + methods: { + processNodes(nodes) { + return nodes.slice(0, 3).map( + ({ + achievement, + createdAt, + achievement: { + namespace: { fullPath }, + }, + }) => { + return { + id: `user-achievement-${getIdFromGraphQLId(achievement.id)}`, + name: achievement.name, + timeAgo: this.timeFormatted(createdAt), + avatarUrl: achievement.avatarUrl || gon.gitlab_logo, + description: achievement.description, + namespace: { + fullPath, + webUrl: this.rootUrl + fullPath, + }, + }; + }, + ); + }, + }, + i18n: { + awardedBy: s__('Achievements|Awarded %{timeAgo} by %{namespace}'), + }, +}; +</script> + +<template> + <div class="gl-mb-3"> + <div + v-for="userAchievement in userAchievements" + :key="userAchievement.id" + class="gl-display-inline-block" + data-testid="user-achievement" + > + <img + :id="userAchievement.id" + :src="userAchievement.avatarUrl" + :alt="''" + tabindex="0" + class="gl-avatar gl-avatar-s32 gl-mx-2" + /> + <gl-popover triggers="hover focus" placement="top" :target="userAchievement.id"> + <div class="gl-font-weight-bold">{{ userAchievement.name }}</div> + <div> + <gl-sprintf :message="$options.i18n.awardedBy"> + <template #timeAgo> + <span>{{ userAchievement.timeAgo }}</span> + </template> + <template #namespace> + <a :href="userAchievement.namespace.webUrl">{{ + userAchievement.namespace.fullPath + }}</a> + </template> + </gl-sprintf> + </div> + <div + v-if="userAchievement.description" + class="gl-mt-5" + data-testid="achievement-description" + > + {{ userAchievement.description }} + </div> + </gl-popover> + </div> + </div> +</template> diff --git a/app/assets/javascripts/profile/index.js b/app/assets/javascripts/profile/index.js index fe430ccca98..fbe0e3534d8 100644 --- a/app/assets/javascripts/profile/index.js +++ b/app/assets/javascripts/profile/index.js @@ -1,6 +1,12 @@ import Vue from 'vue'; +import VueApollo from 'vue-apollo'; + +import createDefaultClient from '~/lib/graphql'; import ProfileTabs from './components/profile_tabs.vue'; +import UserAchievements from './components/user_achievements.vue'; + +Vue.use(VueApollo); export const initProfileTabs = () => { const el = document.getElementById('js-profile-tabs'); @@ -22,3 +28,25 @@ export const initProfileTabs = () => { }, }); }; + +export const initUserAchievements = () => { + const el = document.getElementById('js-user-achievements'); + + if (!el) return false; + + const { rootUrl, userId } = el.dataset; + + const apolloProvider = new VueApollo({ + defaultClient: createDefaultClient(), + }); + + return new Vue({ + el, + apolloProvider, + name: 'UserAchievements', + provide: { rootUrl, userId: parseInt(userId, 10) }, + render(createElement) { + return createElement(UserAchievements); + }, + }); +}; diff --git a/app/assets/stylesheets/page_bundles/profile.scss b/app/assets/stylesheets/page_bundles/profile.scss index 36a53c2ad7d..dfc86a73635 100644 --- a/app/assets/stylesheets/page_bundles/profile.scss +++ b/app/assets/stylesheets/page_bundles/profile.scss @@ -171,7 +171,6 @@ } .avatar-holder { - width: 90px; margin: 0 auto 10px; } } diff --git a/app/assets/stylesheets/page_bundles/settings.scss b/app/assets/stylesheets/page_bundles/settings.scss index 8978b8d798b..9a0d7880734 100644 --- a/app/assets/stylesheets/page_bundles/settings.scss +++ b/app/assets/stylesheets/page_bundles/settings.scss @@ -138,42 +138,32 @@ border-radius: $gl-border-radius-base; } -.prometheus-metrics-monitoring { - .card { - .card-toggle { - width: 14px; - } +.prometheus-metrics-monitoring { + .gl-card { .badge.badge-pill { font-size: 12px; line-height: 12px; } - .card-header .label-count { + .gl-card-header .label-count { color: var(--white, $white); background: var(--gray-800, $gray-800); } - .card-body { - padding: 0; - } - .flash-container { margin-bottom: 0; cursor: default; - .flash-notice { + .flash-notice, + .flash-warning { + margin-top: 0; border-radius: 0; } } } .custom-monitored-metrics { - .card-header { - display: flex; - align-items: center; - } - .custom-metric { display: flex; align-items: center; diff --git a/app/finders/merge_requests_finder.rb b/app/finders/merge_requests_finder.rb index ffa912afd1e..0ce2d52f168 100644 --- a/app/finders/merge_requests_finder.rb +++ b/app/finders/merge_requests_finder.rb @@ -36,6 +36,7 @@ class MergeRequestsFinder < IssuableFinder def self.scalar_params @scalar_params ||= super + [ + :approved, :approved_by_ids, :deployed_after, :deployed_before, @@ -71,8 +72,9 @@ class MergeRequestsFinder < IssuableFinder items = by_approvals(items) items = by_deployments(items) items = by_reviewer(items) + items = by_source_project_id(items) - by_source_project_id(items) + by_approved(items) end def filter_negated_items(items) @@ -183,6 +185,17 @@ class MergeRequestsFinder < IssuableFinder end # rubocop: enable CodeReuse/Finder + def by_approved(items) + approved_param = Gitlab::Utils.to_boolean(params.fetch(:approved, nil)) + return items if approved_param.nil? + + if approved_param + items.with_approvals + else + items.without_approvals + end + end + def by_deployments(items) env = params[:environment] before = parse_datetime(params[:deployed_before]) diff --git a/app/graphql/resolvers/achievements/user_achievements_resolver.rb b/app/graphql/resolvers/achievements/user_achievements_resolver.rb index 594fb3419d6..bf09d80afc1 100644 --- a/app/graphql/resolvers/achievements/user_achievements_resolver.rb +++ b/app/graphql/resolvers/achievements/user_achievements_resolver.rb @@ -8,7 +8,7 @@ module Resolvers type ::Types::Achievements::UserAchievementType.connection_type, null: true def resolve_with_lookahead - user_achievements = object.user_achievements + user_achievements = object.user_achievements.not_revoked apply_lookahead(user_achievements) end diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index e9656cc029b..fd684ee5ecb 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -403,6 +403,7 @@ module ApplicationSettingsHelper :protected_paths_raw, :time_tracking_limit_to_hours, :two_factor_grace_period, + :update_runner_versions_enabled, :unique_ips_limit_enabled, :unique_ips_limit_per_user, :unique_ips_limit_time_window, diff --git a/app/models/achievements/user_achievement.rb b/app/models/achievements/user_achievement.rb index e3f810f8846..bc5d10923d7 100644 --- a/app/models/achievements/user_achievement.rb +++ b/app/models/achievements/user_achievement.rb @@ -14,6 +14,8 @@ module Achievements inverse_of: :revoked_user_achievements, optional: true + scope :not_revoked, -> { where(revoked_by_user_id: nil) } + def revoked? revoked_by_user_id.present? end diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index 2e6c4a53f00..71434931d8c 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -638,7 +638,12 @@ class ApplicationSetting < MainClusterwide::ApplicationRecord length: { maximum: 100, message: N_('is too long (maximum is 100 entries)') }, allow_nil: false - validates :public_runner_releases_url, addressable_url: ADDRESSABLE_URL_VALIDATION_OPTIONS, presence: true + validates :update_runner_versions_enabled, + inclusion: { in: [true, false], message: N_('must be a boolean value') } + validates :public_runner_releases_url, + addressable_url: ADDRESSABLE_URL_VALIDATION_OPTIONS, + presence: true, + if: :update_runner_versions_enabled? validates :inactive_projects_min_size_mb, numericality: { only_integer: true, greater_than_or_equal_to: 0 } diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb index eacb3ab8cd6..6fefe95769b 100644 --- a/app/models/ci/runner.rb +++ b/app/models/ci/runner.rb @@ -604,7 +604,7 @@ module Ci # For now, heartbeats with version updates might result in two Sidekiq jobs being queued if a runner has a system_id # This is not a problem since the jobs are deduplicated on the version def schedule_runner_version_update(new_version) - return unless new_version + return unless new_version && Gitlab::Ci::RunnerReleases.instance.enabled? Ci::Runners::ProcessRunnerVersionUpdateWorker.perform_async(new_version) end diff --git a/app/models/ci/runner_machine.rb b/app/models/ci/runner_machine.rb index 384d716cf2d..8cf395aadb4 100644 --- a/app/models/ci/runner_machine.rb +++ b/app/models/ci/runner_machine.rb @@ -101,7 +101,7 @@ module Ci end def schedule_runner_version_update(new_version) - return unless new_version + return unless new_version && Gitlab::Ci::RunnerReleases.instance.enabled? Ci::Runners::ProcessRunnerVersionUpdateWorker.perform_async(new_version) end diff --git a/app/services/ci/runners/process_runner_version_update_service.rb b/app/services/ci/runners/process_runner_version_update_service.rb index c8a5e42ccab..5c42a2ab018 100644 --- a/app/services/ci/runners/process_runner_version_update_service.rb +++ b/app/services/ci/runners/process_runner_version_update_service.rb @@ -8,6 +8,7 @@ module Ci end def execute + return ServiceResponse.error(message: 'version update disabled') unless enabled? return ServiceResponse.error(message: 'version not present') unless @version _, status = upgrade_check_service.check_runner_upgrade_suggestion(@version) @@ -22,6 +23,10 @@ module Ci def upgrade_check_service @runner_upgrade_check ||= Gitlab::Ci::RunnerUpgradeCheck.new(::Gitlab::VERSION) end + + def enabled? + Gitlab::Ci::RunnerReleases.instance.enabled? + end end end end diff --git a/app/views/admin/application_settings/_runner_registrars_form.html.haml b/app/views/admin/application_settings/_runner_registrars_form.html.haml index baf7c5de7b9..53832e93ed2 100644 --- a/app/views/admin/application_settings/_runner_registrars_form.html.haml +++ b/app/views/admin/application_settings/_runner_registrars_form.html.haml @@ -2,7 +2,18 @@ = form_errors(@application_setting) %fieldset + .form-group + %h5 + = s_('Runners|Runner version management') + %span.form-text.gl-mb-3.gl-mt-0 + - help_text = s_('Runners|Official runner version data is periodically fetched from GitLab.com to determine whether the runners need upgrades.') + - learn_more_link = link_to _('Learn more.'), help_page_path('ci/runners/configure_runners.md', anchor: 'determine-which-runners-need-to-be-upgraded'), target: '_blank', rel: 'noopener noreferrer' + = f.gitlab_ui_checkbox_component :update_runner_versions_enabled, + s_('Runners|Fetch GitLab Runner release version data from GitLab.com'), + help_text: '%{help_text} %{learn_more_link}'.html_safe % { help_text: help_text, learn_more_link: learn_more_link } .gl-form-group + %h5 + = s_('Runners|Runner registration') %span.form-text.gl-mb-3.gl-mt-0 = s_('Runners|If both settings are disabled, new runners cannot be registered.') = link_to _('Learn more.'), help_page_path('user/admin_area/settings/continuous_integration', anchor: 'restrict-runner-registration-by-all-users-in-an-instance'), target: '_blank', rel: 'noopener noreferrer' diff --git a/app/views/admin/application_settings/ci_cd.html.haml b/app/views/admin/application_settings/ci_cd.html.haml index a7119af7ece..c2dc3c3707e 100644 --- a/app/views/admin/application_settings/ci_cd.html.haml +++ b/app/views/admin/application_settings/ci_cd.html.haml @@ -41,7 +41,7 @@ %section.settings.as-runner.no-animate#js-runner-settings{ class: ('expanded' if expanded_by_default?) } .settings-header %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only - = s_('Runners|Runner registration') + = s_('Runners|Runners') = render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do = expanded_by_default? ? 'Collapse' : 'Expand' .settings-content diff --git a/app/views/shared/integrations/prometheus/_custom_metrics.html.haml b/app/views/shared/integrations/prometheus/_custom_metrics.html.haml index dda84e0fb9e..e5ddc055aef 100644 --- a/app/views/shared/integrations/prometheus/_custom_metrics.html.haml +++ b/app/views/shared/integrations/prometheus/_custom_metrics.html.haml @@ -6,13 +6,13 @@ = link_to s_('PrometheusService|More information'), help_page_path('operations/metrics/index.md', anchor: 'adding-custom-metrics'), target: '_blank', rel: "noopener noreferrer" .col-lg-9 - .card.custom-monitored-metrics.js-panel-custom-monitored-metrics{ data: { active_custom_metrics: project_prometheus_metrics_path(project), environments_data: environments_list_data, service_active: "#{integration.active}" } } - .card-header + = render Pajamas::CardComponent.new(header_options: { class: 'gl-display-flex gl-align-items-center' }, body_options: { class: 'gl-p-0' }, card_options: { class: 'gl-mb-5 custom-monitored-metrics js-panel-custom-monitored-metrics', data: { active_custom_metrics: project_prometheus_metrics_path(project), environments_data: environments_list_data, service_active: "#{integration.active}" } }) do |c| + - c.header do %strong = s_('PrometheusService|Custom metrics') - = gl_badge_tag 0, nil, class: 'js-custom-monitored-count' + = gl_badge_tag 0, nil, class: 'gl-ml-2 js-custom-monitored-count' = link_to s_('PrometheusService|New metric'), new_project_prometheus_metric_path(project), class: 'btn gl-button btn-confirm gl-ml-auto js-new-metric-button hidden' - .card-body + - c.body do .flash-container.hidden .flash-warning .flash-text diff --git a/app/views/shared/integrations/prometheus/_metrics.html.haml b/app/views/shared/integrations/prometheus/_metrics.html.haml index a8de6b001bd..a8125c3e3ec 100644 --- a/app/views/shared/integrations/prometheus/_metrics.html.haml +++ b/app/views/shared/integrations/prometheus/_metrics.html.haml @@ -8,29 +8,28 @@ = link_to s_('PrometheusService|More information'), help_page_path('user/project/integrations/prometheus'), target: '_blank', rel: "noopener noreferrer" .col-lg-9 - .card.js-panel-monitored-metrics{ data: { active_metrics: active_common_project_prometheus_metrics_path(project, :json), metrics_help_path: help_page_path('user/project/integrations/prometheus_library/index') } } - = render Pajamas::CardComponent.new(body_options: { class: 'gl-p-0' }) do |c| - - c.header do - %strong - = s_('PrometheusService|Common metrics') - = gl_badge_tag 0, nil, class: 'js-monitored-count' - - c.body do - .loading-metrics.js-loading-metrics - %p.m-3 - = gl_loading_icon(inline: true, css_class: 'metrics-load-spinner') - = s_('PrometheusService|Finding and configuring metrics...') - .empty-metrics.hidden.js-empty-metrics - %p.text-tertiary.m-3 - = s_('PrometheusService|Waiting for your first deployment to an environment to find common metrics') - %ul.list-unstyled.metrics-list.hidden.js-metrics-list + = render Pajamas::CardComponent.new(body_options: { class: 'gl-p-0' }, card_options: { class: 'gl-mb-5 js-panel-monitored-metrics', data: { active_metrics: active_common_project_prometheus_metrics_path(project, :json), metrics_help_path: help_page_path('user/project/integrations/prometheus_library/index') }}) do |c| + - c.header do + %strong + = s_('PrometheusService|Common metrics') + = gl_badge_tag 0, nil, class: 'js-monitored-count' + - c.body do + .loading-metrics.js-loading-metrics + %p.m-3 + = gl_loading_icon(inline: true, css_class: 'metrics-load-spinner') + = s_('PrometheusService|Finding and configuring metrics...') + .empty-metrics.hidden.js-empty-metrics + %p.text-tertiary.m-3 + = s_('PrometheusService|Waiting for your first deployment to an environment to find common metrics') + %ul.list-unstyled.metrics-list.hidden.js-metrics-list - .card.hidden.js-panel-missing-env-vars - .card-header + = render Pajamas::CardComponent.new(body_options: { class: 'hidden gl-p-0' }, card_options: { class: 'hidden js-panel-missing-env-vars' }) do |c| + - c.header do = sprite_icon('chevron-lg-right', css_class: 'panel-toggle js-panel-toggle-right') = sprite_icon('chevron-lg-down', css_class: 'panel-toggle js-panel-toggle-down hidden') = s_('PrometheusService|Missing environment variable') = gl_badge_tag 0, nil, class: 'js-env-var-count' - .card-body.hidden + - c.body do .flash-container .flash-notice .flash-text diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml index 72940b64801..95c5f51c339 100644 --- a/app/views/shared/issuable/_search_bar.html.haml +++ b/app/views/shared/issuable/_search_bar.html.haml @@ -162,6 +162,14 @@ %li.filter-dropdown-item{ data: { value: 'no', capitalize: true } } %button.gl-button.btn.btn-link{ type: 'button' } = _('No') + #js-dropdown-approved.filtered-search-input-dropdown-menu.dropdown-menu + %ul.filter-dropdown{ data: { dropdown: true } } + %li.filter-dropdown-item{ data: { value: 'yes', capitalize: true } } + %button.gl-button.btn.btn-link{ type: 'button' } + = _('Yes') + %li.filter-dropdown-item{ data: { value: 'no', capitalize: true } } + %button.gl-button.btn.btn-link{ type: 'button' } + = _('No') #js-dropdown-confidential.filtered-search-input-dropdown-menu.dropdown-menu %ul.filter-dropdown{ data: { dropdown: true } } %li.filter-dropdown-item{ data: { value: 'yes', capitalize: true } } diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index c27086ded6f..3543d5c4336 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -50,6 +50,7 @@ .avatar-holder = link_to avatar_icon_for_user(@user, 400, current_user: current_user), target: '_blank', rel: 'noopener noreferrer' do = render Pajamas::AvatarComponent.new(@user, alt: "", size: 96, avatar_options: { itemprop: "image" }) + #js-user-achievements{ data: { root_url: root_url, user_id: @user.id } } .gl-display-inline-block.gl-vertical-align-top.gl-text-left - if @user.blocked? || !@user.confirmed? .user-info |