Module: ApplicationHelper

Class Method Summary collapse

Instance Method Summary collapse

Methods included from ViteHelper

#universal_path_to_stylesheet, #universal_stylesheet_link_tag, #vite_enabled?, #vite_page_entrypoint_paths

Class Method Details

.community_forumObject

This needs to be used outside of Rails



233
234
235
# File 'app/helpers/application_helper.rb', line 233

def self.community_forum
  'https://forum.gitlab.com'
end

Instance Method Details

#active_when(condition) ⇒ Object

Returns active css class when condition returns true otherwise returns nil.

Example:

%li{ class: active_when(params[:filter] == '1') }


344
345
346
# File 'app/helpers/application_helper.rb', line 344

def active_when(condition)
  'active' if condition
end

#add_issuable_stylesheetObject



447
448
449
450
# File 'app/helpers/application_helper.rb', line 447

def add_issuable_stylesheet
  add_page_specific_style('page_bundles/issuable')
  add_page_specific_style('page_bundles/notes_shared')
end

#add_page_specific_style(path) ⇒ Object



432
433
434
435
436
437
438
439
440
# File 'app/helpers/application_helper.rb', line 432

def add_page_specific_style(path)
  @already_added_styles ||= Set.new
  return if @already_added_styles.include?(path)

  @already_added_styles.add(path)
  content_for :page_specific_styles do
    universal_stylesheet_link_tag path
  end
end

#add_page_startup_api_call(api_path, options: {}) ⇒ Object



456
457
458
459
# File 'app/helpers/application_helper.rb', line 456

def add_page_startup_api_call(api_path, options: {})
  @api_startup_calls ||= {}
  @api_startup_calls[api_path] = options
end

#add_work_items_stylesheetObject



442
443
444
445
# File 'app/helpers/application_helper.rb', line 442

def add_work_items_stylesheet
  add_page_specific_style('page_bundles/work_items')
  add_page_specific_style('page_bundles/notes_shared')
end

#admin_section?Boolean

Returns:

  • (Boolean)


90
91
92
# File 'app/helpers/application_helper.rb', line 90

def admin_section?
  controller.class.ancestors.include?(Admin::ApplicationController)
end

#ai_panel_expanded?Boolean

Returns:

  • (Boolean)


524
525
526
# File 'app/helpers/application_helper.rb', line 524

def ai_panel_expanded?
  cookies[:ai_panel_active_tab].present?
end

#asset_to_string(name) ⇒ Object



488
489
490
491
492
493
494
495
# File 'app/helpers/application_helper.rb', line 488

def asset_to_string(name)
  app = Rails.application
  if Rails.configuration.assets.compile
    app.assets.find_asset(name).to_s
  else
    controller.view_context.render(file: Rails.root.join('public/assets', app.assets_manifest.assets[name]).to_s)
  end
end

#autocomplete_data_sources(object, noteable_type) ⇒ Object



461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
# File 'app/helpers/application_helper.rb', line 461

def autocomplete_data_sources(object, noteable_type)
  return {} unless object && noteable_type

  if object.is_a?(Group)
    {
      members: members_group_autocomplete_sources_path(object, type: noteable_type, type_id: params[:id]),
      issues: issues_group_autocomplete_sources_path(object),
      mergeRequests: merge_requests_group_autocomplete_sources_path(object),
      labels: labels_group_autocomplete_sources_path(object, type: noteable_type, type_id: params[:id]),
      milestones: milestones_group_autocomplete_sources_path(object),
      commands: commands_group_autocomplete_sources_path(object, type: noteable_type, type_id: params[:id])
    }.compact
  else
    {
      members: members_project_autocomplete_sources_path(object, type: noteable_type, type_id: params[:id]),
      issues: issues_project_autocomplete_sources_path(object),
      mergeRequests: merge_requests_project_autocomplete_sources_path(object),
      labels: labels_project_autocomplete_sources_path(object, type: noteable_type, type_id: params[:id]),
      milestones: milestones_project_autocomplete_sources_path(object),
      commands: commands_project_autocomplete_sources_path(object, type: noteable_type, type_id: params[:id]),
      snippets: snippets_project_autocomplete_sources_path(object),
      contacts: contacts_project_autocomplete_sources_path(object, type: noteable_type, type_id: params[:id]),
      wikis: object.feature_available?(:wiki, current_user) ? wikis_project_autocomplete_sources_path(object) : nil
    }.compact
  end
end

#bluesky_url(user) ⇒ Object



376
377
378
379
380
# File 'app/helpers/application_helper.rb', line 376

def bluesky_url(user)
  return '' if user.bluesky.blank?

  external_redirect_path(url: "https://bsky.app/profile/#{user.bluesky}")
end

#body_dataObject



138
139
140
141
142
143
144
145
146
# File 'app/helpers/application_helper.rb', line 138

def body_data
  {
    page: body_data_page,
    page_type_id: controller.params[:id],
    group: @group&.path,
    group_full_path: @group&.full_path,
    project_studio_enabled: project_studio_enabled?.to_s
  }.merge(project_data)
end

#body_data_pageObject



161
162
163
# File 'app/helpers/application_helper.rb', line 161

def body_data_page
  [*controller.controller_path.split('/'), controller.action_name].compact.join(':')
end

#client_class_listObject



421
422
423
# File 'app/helpers/application_helper.rb', line 421

def client_class_list
  "gl-browser-#{browser_id} gl-platform-#{platform_id}" # rubocop:disable Tailwind/StringInterpolation -- Not a CSS utility class
end

#client_js_flagsObject



425
426
427
428
429
430
# File 'app/helpers/application_helper.rb', line 425

def client_js_flags
  {
    "is#{browser_id.titlecase}": true,
    "is#{platform_id.titlecase}": true
  }
end

#collapsed_super_sidebar?Boolean

Returns:

  • (Boolean)


404
405
406
407
408
# File 'app/helpers/application_helper.rb', line 404

def collapsed_super_sidebar?
  return false if @force_desktop_expanded_sidebar

  cookies["super_sidebar_collapsed"] == "true"
end

#community_forumObject

Convenient method for Rails helper



242
243
244
# File 'app/helpers/application_helper.rb', line 242

def community_forum
  ApplicationHelper.community_forum
end

While similarly named to Rails’s link_to_if, this method behaves quite differently. If condition is truthy, a link will be returned with the result of the block as its body. If condition is falsy, only the result of the block will be returned.



306
307
308
309
310
311
312
# File 'app/helpers/application_helper.rb', line 306

def conditional_link_to(condition, options, html_options = {}, &block)
  if condition
    link_to options, html_options, &block
  else
    capture(&block)
  end
end

#current_action?(*args) ⇒ Boolean

Check if a particular action is the current one

args - One or more action names to check

Examples

# On Projects#new
current_action?(:new)           # => true
current_action?(:create)        # => false
current_action?(:new, :create)  # => true

Returns:

  • (Boolean)


86
87
88
# File 'app/helpers/application_helper.rb', line 86

def current_action?(*args)
  args.any? { |v| Gitlab::Utils.safe_downcase!(v.to_s) == action_name }
end

#current_controller?(*args) ⇒ Boolean

Check if a particular controller is the current one

args - One or more controller names to check (using path notation when inside namespaces)

Examples

# On TreeController
current_controller?(:tree)           # => true
current_controller?(:commits)        # => false
current_controller?(:commits, :tree) # => true

# On Admin::ApplicationController
current_controller?(:application)         # => true
current_controller?('admin/application')  # => true
current_controller?('gitlab/application') # => false

Returns:

  • (Boolean)


70
71
72
73
74
# File 'app/helpers/application_helper.rb', line 70

def current_controller?(*args)
  args.any? do |v|
    Gitlab::Utils.safe_downcase!(v.to_s) == controller.controller_name || Gitlab::Utils.safe_downcase!(v.to_s) == controller.controller_path
  end
end

#discord_url(user) ⇒ Object



370
371
372
373
374
# File 'app/helpers/application_helper.rb', line 370

def discord_url(user)
  return '' if user.discord.blank?

  "https://discord.com/users/#{user.discord}"
end

#dispensable_renderObject



22
23
24
25
26
27
# File 'app/helpers/application_helper.rb', line 22

def dispensable_render(...)
  render(...)
rescue StandardError => e
  Gitlab::ErrorTracking.track_and_raise_for_dev_exception(e)
  nil
end

#dispensable_render_if_existsObject



29
30
31
32
33
34
# File 'app/helpers/application_helper.rb', line 29

def dispensable_render_if_exists(...)
  render_if_exists(...)
rescue StandardError => e
  Gitlab::ErrorTracking.track_and_raise_for_dev_exception(e)
  nil
end

#edited_time_ago_with_tooltip(editable_object, placement: 'top', html_class: 'time_ago', exclude_author: false) ⇒ Object



215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
# File 'app/helpers/application_helper.rb', line 215

def edited_time_ago_with_tooltip(editable_object, placement: 'top', html_class: 'time_ago', exclude_author: false)
  return unless editable_object.edited?

   :div, class: 'edited-text gl-mt-4 gl-text-subtle gl-text-sm' do
    timeago = time_ago_with_tooltip(editable_object.last_edited_at, placement: placement, html_class: html_class)

    if !exclude_author && editable_object.last_edited_by
      author_link = link_to_member(editable_object.last_edited_by, avatar: false, extra_class: 'hover:gl-underline gl-text-subtle', author_class: nil)
      output = safe_format(_("Edited %{timeago} by %{author}"), timeago: timeago, author: author_link)
    else
      output = safe_format(_("Edited %{timeago}"), timeago: timeago)
    end

    output
  end
end

#error_cssObject

rubocop: enable CodeReuse/ActiveRecord



45
46
47
48
49
50
51
52
53
# File 'app/helpers/application_helper.rb', line 45

def error_css
  Rails.application
    .assets_manifest
    .find_sources('errors.css')
    .first
    .to_s
    .force_encoding('UTF-8') # See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/145363
    .html_safe # rubocop:disable Rails/OutputSafety -- No escaping needed
end

#external_storage_url_or_path(path, project = @project) ⇒ Object



258
259
260
261
262
263
264
265
266
267
268
269
270
271
# File 'app/helpers/application_helper.rb', line 258

def external_storage_url_or_path(path, project = @project)
  return path if @snippet || !static_objects_external_storage_enabled?

  uri = URI(Gitlab::CurrentSettings.static_objects_external_storage_url)
  path = URI(path) # `path` could have query parameters, so we need to split query and path apart

  query = Rack::Utils.parse_nested_query(path.query)
  query['token'] = current_user.static_object_token unless project.public?

  uri.path = path.path
  uri.query = query.to_query unless query.empty?

  uri.to_s
end

#extra_configObject

shortcut for gitlab extra config



171
172
173
# File 'app/helpers/application_helper.rb', line 171

def extra_config
  Gitlab.config.extra
end

#github_url(user) ⇒ Object



398
399
400
401
402
# File 'app/helpers/application_helper.rb', line 398

def github_url(user)
  return '' if user.github.blank?

  "https://github.com/#{user.github}"
end

#gitlab_configObject

shortcut for gitlab config



166
167
168
# File 'app/helpers/application_helper.rb', line 166

def gitlab_config
  Gitlab.config.gitlab
end

#gitlab_ui_form_for(record, *args, &block) ⇒ Object



497
498
499
500
501
# File 'app/helpers/application_helper.rb', line 497

def gitlab_ui_form_for(record, *args, &block)
  options = args.extract_options!

  form_for(record, *(args << options.merge({ builder: ::Gitlab::FormBuilders::GitlabUiFormBuilder })), &block)
end

#gitlab_ui_form_with(**args, &block) ⇒ Object



503
504
505
# File 'app/helpers/application_helper.rb', line 503

def gitlab_ui_form_with(**args, &block)
  form_with(**args.merge({ builder: ::Gitlab::FormBuilders::GitlabUiFormBuilder }), &block)
end

#hexdigest(string) ⇒ Object

rubocop: enable CodeReuse/ActiveRecord



126
127
128
# File 'app/helpers/application_helper.rb', line 126

def hexdigest(string)
  Digest::SHA1.hexdigest string
end

#hidden_resource_icon(resource, css_class: nil, variant: nil) ⇒ Object



507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
# File 'app/helpers/application_helper.rb', line 507

def hidden_resource_icon(resource, css_class: nil, variant: nil)
  issuable_title = _('This %{issuable} is hidden because its author has been banned.')

  case resource
  when Issue
    title = format(issuable_title, issuable: _('issue'))
  when MergeRequest
    title = format(issuable_title, issuable: _('merge request'))
  end

  return unless title

  (:span, class: 'has-tooltip', title: title) do
    sprite_icon('spam', css_class: ['gl-align-text-bottom', css_class].compact_blank.join(' '), variant: variant)
  end
end

#instance_review_permitted?Boolean

Returns:

  • (Boolean)


250
251
252
# File 'app/helpers/application_helper.rb', line 250

def instance_review_permitted?
  ::Gitlab::CurrentSettings.instance_review_permitted? && current_user&.can_read_all_resources?
end

#last_commit(project) ⇒ Object



94
95
96
97
98
99
100
101
102
# File 'app/helpers/application_helper.rb', line 94

def last_commit(project)
  if project.repo_exists?
    time_ago_with_tooltip(project.repository.commit.committed_date)
  else
    'Never'
  end
rescue StandardError
  'Never'
end

#linkedin_name(user) ⇒ Object



352
353
354
# File 'app/helpers/application_helper.rb', line 352

def linkedin_name(user)
  user.linkedin.chomp('/').gsub(%r{.*/}, '')
end

#linkedin_url(user) ⇒ Object



356
357
358
359
# File 'app/helpers/application_helper.rb', line 356

def linkedin_url(user)
  name = linkedin_name(user)
  "https://www.linkedin.com/in/#{name}"
end

#locale_pathObject



410
411
412
# File 'app/helpers/application_helper.rb', line 410

def locale_path
  asset_path("locale/#{Gitlab::I18n.locale}/app.js")
end

#mastodon_url(user) ⇒ Object



388
389
390
391
392
393
394
395
396
# File 'app/helpers/application_helper.rb', line 388

def mastodon_url(user)
  return '' if user.mastodon.blank?

  url = user.mastodon.match UserDetail::MASTODON_VALIDATION_REGEX

  return '' unless url

  external_redirect_path(url: "https://#{url[2]}/@#{url[1]}", rel: 'me')
end

#orcid_url(user) ⇒ Object



382
383
384
385
386
# File 'app/helpers/application_helper.rb', line 382

def orcid_url(user)
  return '' if user.orcid.blank?

  external_redirect_path(url: "https://orcid.org/#{user.orcid}")
end

#outdated_browser?Boolean

Returns:

  • (Boolean)


291
292
293
# File 'app/helpers/application_helper.rb', line 291

def outdated_browser?
  browser.ie?
end

#page_classObject



314
315
316
317
318
319
320
321
322
323
324
325
326
# File 'app/helpers/application_helper.rb', line 314

def page_class
  class_names = ['with-top-bar']
  class_names << 'issue-boards-page gl-overflow-auto' if current_controller?(:boards)
  class_names << 'epic-boards-page gl-overflow-auto' if current_controller?(:epic_boards)
  class_names << 'with-performance-bar' if performance_bar_enabled?
  class_names << 'with-header' if @with_header || !current_user || project_studio_enabled?
  class_names << 'application-chrome' if project_studio_enabled?
  class_names << 'page-with-panels' if project_studio_enabled?
  class_names << 'with-gl-container-queries' if project_studio_enabled?
  class_names << system_message_class

  class_names
end

#page_filter_path(options = {}) ⇒ Object



273
274
275
276
277
278
279
280
281
282
283
284
285
# File 'app/helpers/application_helper.rb', line 273

def page_filter_path(options = {})
  without = options.delete(:without)

  options = request.query_parameters.merge(options)

  if without.present?
    without.each do |key|
      options.delete(key)
    end
  end

  "#{request.path}?#{options.compact.to_param}"
end

#page_startup_api_callsObject



452
453
454
# File 'app/helpers/application_helper.rb', line 452

def page_startup_api_calls
  @api_startup_calls
end

#partial_exists?(partial) ⇒ Boolean

Returns:

  • (Boolean)


36
37
38
# File 'app/helpers/application_helper.rb', line 36

def partial_exists?(partial)
  lookup_context.exists?(partial, [], true)
end

#path_to_key(key, admin = false) ⇒ Object



295
296
297
298
299
300
301
# File 'app/helpers/application_helper.rb', line 295

def path_to_key(key, admin = false)
  if admin
    admin_user_key_path(@user, key)
  else
    (key)
  end
end

#project_dataObject



148
149
150
151
152
153
154
155
156
157
158
159
# File 'app/helpers/application_helper.rb', line 148

def project_data
  return {} unless @project

  {
    project_id: @project.id,
    project: @project.path,
    project_full_path: @project.full_path,
    group: @project.group&.path,
    group_full_path: @project.group&.full_path,
    namespace_id: @project.namespace&.id
  }
end

#project_studio_enabled?Boolean

Returns:

  • (Boolean)


134
135
136
# File 'app/helpers/application_helper.rb', line 134

def project_studio_enabled?
  true
end

#read_only_messageObject

Overridden in EE



415
416
417
418
419
# File 'app/helpers/application_helper.rb', line 415

def read_only_message
  return unless Gitlab::Database.read_only?

  _('You are on a read-only GitLab instance.')
end

#registry_configObject

shortcut for gitlab registry config



176
177
178
# File 'app/helpers/application_helper.rb', line 176

def registry_config
  Gitlab.config.registry
end

#render_if_exists(partial = nil, **options) ⇒ Object

See docs.gitlab.com/ee/development/ee_features.html#code-in-appviews rubocop: disable CodeReuse/ActiveRecord We allow partial to be nil so that collection views can be passed in ‘render partial: ’some/view’, collection: @some_collection`



12
13
14
15
16
17
18
19
20
# File 'app/helpers/application_helper.rb', line 12

def render_if_exists(partial = nil, **options)
  return unless partial_exists?(partial || options[:partial])

  if partial.nil?
    render(**options)
  else
    render(partial, options)
  end
end

#show_callout?(name) ⇒ Boolean

Returns:

  • (Boolean)


348
349
350
# File 'app/helpers/application_helper.rb', line 348

def show_callout?(name)
  cookies[name] != 'true'
end

#show_last_push_widget?(event) ⇒ Boolean

Define whenever show last push event with suggestion to create MR rubocop: disable CodeReuse/ActiveRecord

Returns:

  • (Boolean)


107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'app/helpers/application_helper.rb', line 107

def show_last_push_widget?(event)
  # Skip if event is not about added or modified non-master branch
  return false unless event && event.last_push_to_non_root? && !event.rm_ref?

  project = event.project

  # Skip if project repo is empty or MR disabled
  return false unless project && !project.empty_repo? && project.feature_available?(:merge_requests, current_user)

  # Skip if user already created appropriate MR
  return false if project.merge_requests.where(source_branch: event.branch_name).opened.any?

  # Skip if user removed branch right after that
  return false unless project.repository.branch_exists?(event.branch_name)

  true
end

#sign_in_with_redirect?Boolean

Returns:

  • (Boolean)


287
288
289
# File 'app/helpers/application_helper.rb', line 287

def 
  current_page?(new_user_session_path) && session[:user_return_to].present?
end

#simple_sanitize(str) ⇒ Object



130
131
132
# File 'app/helpers/application_helper.rb', line 130

def simple_sanitize(str)
  sanitize(str, tags: %w[a span])
end

#static_objects_external_storage_enabled?Boolean

Returns:

  • (Boolean)


254
255
256
# File 'app/helpers/application_helper.rb', line 254

def static_objects_external_storage_enabled?
  Gitlab::CurrentSettings.static_objects_external_storage_enabled?
end

#support_urlObject



246
247
248
# File 'app/helpers/application_helper.rb', line 246

def support_url
  Gitlab::CurrentSettings.current_application_settings.help_page_support_url.presence || promo_url(path: '/get-help/')
end

#system_message_classObject



328
329
330
331
332
333
334
335
336
337
# File 'app/helpers/application_helper.rb', line 328

def system_message_class
  class_names = []

  return class_names unless appearance

  class_names << 'with-system-header' if appearance.show_header?
  class_names << 'with-system-footer' if appearance.show_footer?

  class_names.join(' ')
end

#template_exists?(template) ⇒ Boolean

Returns:

  • (Boolean)


40
41
42
# File 'app/helpers/application_helper.rb', line 40

def template_exists?(template)
  lookup_context.exists?(template, [], false)
end

#time_ago_with_tooltip(time, placement: 'top', html_class: '', short_format: false) ⇒ Object

Render a time element with Javascript-based relative date and tooltip

time - Time object placement - Tooltip placement String (default: “top”) html_class - Custom class for time element (default: “time_ago”)

By default also includes a script element with Javascript necessary to initialize the timeago jQuery extension. If this method is called many times, for example rendering hundreds of commits, it’s advisable to disable this behavior using the skip_js argument and re-initializing timeago manually once all of the elements have been rendered.

A js-timeago class is always added to the element, even when a custom html_class argument is provided.

Returns an HTML-safe String



196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
# File 'app/helpers/application_helper.rb', line 196

def time_ago_with_tooltip(time, placement: 'top', html_class: '', short_format: false)
  return "" if time.nil?

  css_classes = [short_format ? 'js-short-timeago' : 'js-timeago']
  css_classes << html_class unless html_class.blank?

   :time, l(time, format: "%b %d, %Y"),
    class: css_classes.join(' '),
    title: l(time.to_time.in_time_zone, format: :timeago_tooltip),
    datetime: time.to_time.getutc.iso8601,
    tabindex: '0',
    aria: { label: l(time.to_time.in_time_zone, format: :timeago_tooltip) },
    data: {
      toggle: 'tooltip',
      placement: placement,
      container: 'body'
    }
end

#twitter_url(user) ⇒ Object



361
362
363
364
365
366
367
368
# File 'app/helpers/application_helper.rb', line 361

def twitter_url(user)
  name = user.twitter
  if %r{\Ahttps?://(www\.)?twitter\.com/}.match?(name)
    name
  else
    "https://twitter.com/#{name}"
  end
end

#university_urlObject



237
238
239
# File 'app/helpers/application_helper.rb', line 237

def university_url
  'https://university.gitlab.com'
end