diff options
Diffstat (limited to 'app')
30 files changed, 176 insertions, 151 deletions
diff --git a/app/assets/javascripts/ide/components/external_link.vue b/app/assets/javascripts/ide/components/external_link.vue new file mode 100644 index 00000000000..cf3316a8179 --- /dev/null +++ b/app/assets/javascripts/ide/components/external_link.vue @@ -0,0 +1,41 @@ +<script> +import Icon from '~/vue_shared/components/icon.vue'; + +export default { + components: { + Icon, + }, + props: { + file: { + type: Object, + required: true, + }, + }, + computed: { + showButtons() { + return this.file.permalink; + }, + }, +}; +</script> + +<template> + <div + v-if="showButtons" + class="pull-right ide-btn-group" + > + <a + :href="file.permalink" + target="_blank" + :title="s__('IDE|Open in file view')" + rel="noopener noreferrer" + > + <span class="vertical-align-middle">Open in file view</span> + <icon + name="external-link" + css-classes="vertical-align-middle space-right" + :size="16" + /> + </a> + </div> +</template> diff --git a/app/assets/javascripts/ide/components/ide_file_buttons.vue b/app/assets/javascripts/ide/components/ide_file_buttons.vue deleted file mode 100644 index 30b00abf6ed..00000000000 --- a/app/assets/javascripts/ide/components/ide_file_buttons.vue +++ /dev/null @@ -1,84 +0,0 @@ -<script> -import { __ } from '~/locale'; -import tooltip from '~/vue_shared/directives/tooltip'; -import Icon from '~/vue_shared/components/icon.vue'; - -export default { - components: { - Icon, - }, - directives: { - tooltip, - }, - props: { - file: { - type: Object, - required: true, - }, - }, - computed: { - showButtons() { - return ( - this.file.rawPath || this.file.blamePath || this.file.commitsPath || this.file.permalink - ); - }, - rawDownloadButtonLabel() { - return this.file.binary ? __('Download') : __('Raw'); - }, - }, -}; -</script> - -<template> - <div - v-if="showButtons" - class="float-right ide-btn-group" - > - <a - v-tooltip - v-if="!file.binary" - :href="file.blamePath" - :title="__('Blame')" - class="btn btn-sm btn-transparent blame" - > - <icon - name="blame" - :size="16" - /> - </a> - <a - v-tooltip - :href="file.commitsPath" - :title="__('History')" - class="btn btn-sm btn-transparent history" - > - <icon - name="history" - :size="16" - /> - </a> - <a - v-tooltip - :href="file.permalink" - :title="__('Permalink')" - class="btn btn-sm btn-transparent permalink" - > - <icon - name="link" - :size="16" - /> - </a> - <a - v-tooltip - :href="file.rawPath" - target="_blank" - class="btn btn-sm btn-transparent prepend-left-10 raw" - rel="noopener noreferrer" - :title="rawDownloadButtonLabel"> - <icon - name="download" - :size="16" - /> - </a> - </div> -</template> diff --git a/app/assets/javascripts/ide/components/repo_editor.vue b/app/assets/javascripts/ide/components/repo_editor.vue index a281ecb95c8..93453989c08 100644 --- a/app/assets/javascripts/ide/components/repo_editor.vue +++ b/app/assets/javascripts/ide/components/repo_editor.vue @@ -6,12 +6,12 @@ import ContentViewer from '~/vue_shared/components/content_viewer/content_viewer import { activityBarViews, viewerTypes } from '../constants'; import monacoLoader from '../monaco_loader'; import Editor from '../lib/editor'; -import IdeFileButtons from './ide_file_buttons.vue'; +import ExternalLink from './external_link.vue'; export default { components: { ContentViewer, - IdeFileButtons, + ExternalLink, }, props: { file: { @@ -224,7 +224,7 @@ export default { </a> </li> </ul> - <ide-file-buttons + <external-link :file="file" /> </div> diff --git a/app/assets/stylesheets/bootstrap_migration.scss b/app/assets/stylesheets/bootstrap_migration.scss index e1a47f3d686..a40f4ea4f4b 100644 --- a/app/assets/stylesheets/bootstrap_migration.scss +++ b/app/assets/stylesheets/bootstrap_migration.scss @@ -148,8 +148,14 @@ table { } } -.nav-tabs .nav-link { - border: 0; +.nav-tabs { + .nav-link { + border: 0; + } + + .nav-item { + margin-bottom: 0; + } } pre code { diff --git a/app/assets/stylesheets/framework/gitlab_theme.scss b/app/assets/stylesheets/framework/gitlab_theme.scss index d6ae8cbb416..3bbb50117bc 100644 --- a/app/assets/stylesheets/framework/gitlab_theme.scss +++ b/app/assets/stylesheets/framework/gitlab_theme.scss @@ -169,11 +169,14 @@ color: $color-800; } - .nav-links li a.active { - border-bottom: 2px solid $color-500; + .nav-links li { + &.active a, + a.active { + border-bottom: 2px solid $color-500; - .badge.badge-pill { - font-weight: $gl-font-weight-bold; + .badge.badge-pill { + font-weight: $gl-font-weight-bold; + } } } diff --git a/app/assets/stylesheets/framework/secondary_navigation_elements.scss b/app/assets/stylesheets/framework/secondary_navigation_elements.scss index c3c64adf3da..847fc8c0792 100644 --- a/app/assets/stylesheets/framework/secondary_navigation_elements.scss +++ b/app/assets/stylesheets/framework/secondary_navigation_elements.scss @@ -31,14 +31,15 @@ color: $black; } } + } - &.active { - color: $black; - font-weight: $gl-font-weight-bold; + &.active a, + a.active { + color: $black; + font-weight: $gl-font-weight-bold; - .badge.badge-pill { - color: $black; - } + .badge.badge-pill { + color: $black; } } } diff --git a/app/controllers/concerns/issues_action.rb b/app/controllers/concerns/issues_action.rb index 3b11a373368..b6eb7d292fc 100644 --- a/app/controllers/concerns/issues_action.rb +++ b/app/controllers/concerns/issues_action.rb @@ -17,10 +17,23 @@ module IssuesAction end # rubocop:enable Gitlab/ModuleWithInstanceVariables + # rubocop:disable Gitlab/ModuleWithInstanceVariables + def issues_calendar + @issues = issuables_collection + .non_archived + .with_due_date + .limit(100) + + respond_to do |format| + format.ics { response.headers['Content-Disposition'] = 'inline' } + end + end + # rubocop:enable Gitlab/ModuleWithInstanceVariables + private def finder_type (super if defined?(super)) || - (IssuesFinder if action_name == 'issues') + (IssuesFinder if %w(issues issues_calendar).include?(action_name)) end end diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb index 9f5ad23a20f..074db361949 100644 --- a/app/controllers/profiles_controller.rb +++ b/app/controllers/profiles_controller.rb @@ -34,12 +34,12 @@ class ProfilesController < Profiles::ApplicationController redirect_to profile_personal_access_tokens_path end - def reset_rss_token + def reset_feed_token Users::UpdateService.new(current_user, user: @user).execute! do |user| - user.reset_rss_token! + user.reset_feed_token! end - flash[:notice] = "RSS token was successfully reset" + flash[:notice] = 'Feed token was successfully reset' redirect_to profile_personal_access_tokens_path end diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index d69015c8665..35c36c725e2 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -10,8 +10,8 @@ class Projects::IssuesController < Projects::ApplicationController before_action :whitelist_query_limiting, only: [:create, :create_merge_request, :move, :bulk_update] before_action :check_issues_available! - before_action :issue, except: [:index, :new, :create, :bulk_update] - before_action :set_issuables_index, only: [:index] + before_action :issue, except: [:index, :calendar, :new, :create, :bulk_update] + before_action :set_issuables_index, only: [:index, :calendar] # Allow write(create) issue before_action :authorize_create_issue!, only: [:new, :create] @@ -39,6 +39,17 @@ class Projects::IssuesController < Projects::ApplicationController end end + def calendar + @issues = @issuables + .non_archived + .with_due_date + .limit(100) + + respond_to do |format| + format.ics { response.headers['Content-Disposition'] = 'inline' } + end + end + def new params[:issue] ||= ActionController::Parameters.new( assignee_ids: "" diff --git a/app/finders/issues_finder.rb b/app/finders/issues_finder.rb index 1787b4899cd..3626670d141 100644 --- a/app/finders/issues_finder.rb +++ b/app/finders/issues_finder.rb @@ -75,6 +75,8 @@ class IssuesFinder < IssuableFinder items = items.due_between(Date.today.beginning_of_week, Date.today.end_of_week) elsif filter_by_due_this_month? items = items.due_between(Date.today.beginning_of_month, Date.today.end_of_month) + elsif filter_by_due_next_month_and_previous_two_weeks? + items = items.due_between(Date.today - 2.weeks, (Date.today + 1.month).end_of_month) end end @@ -97,6 +99,10 @@ class IssuesFinder < IssuableFinder due_date? && params[:due_date] == Issue::DueThisMonth.name end + def filter_by_due_next_month_and_previous_two_weeks? + due_date? && params[:due_date] == Issue::DueNextMonthAndPreviousTwoWeeks.name + end + def due_date? params[:due_date].present? end diff --git a/app/helpers/calendar_helper.rb b/app/helpers/calendar_helper.rb new file mode 100644 index 00000000000..c54b91b0ce5 --- /dev/null +++ b/app/helpers/calendar_helper.rb @@ -0,0 +1,8 @@ +module CalendarHelper + def calendar_url_options + { format: :ics, + feed_token: current_user.try(:feed_token), + due_date: Issue::DueNextMonthAndPreviousTwoWeeks.name, + sort: 'closest_future_date' } + end +end diff --git a/app/helpers/rss_helper.rb b/app/helpers/rss_helper.rb index 9ac4df88dc3..7d4fa83a67a 100644 --- a/app/helpers/rss_helper.rb +++ b/app/helpers/rss_helper.rb @@ -1,5 +1,5 @@ module RssHelper def rss_url_options - { format: :atom, rss_token: current_user.try(:rss_token) } + { format: :atom, feed_token: current_user.try(:feed_token) } end end diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index b45395343cc..44150b37708 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -97,8 +97,6 @@ module Issuable strip_attributes :title - after_save :ensure_metrics, unless: :imported? - # We want to use optimistic lock for cases when only title or description are involved # http://api.rubyonrails.org/classes/ActiveRecord/Locking/Optimistic.html def locking_enabled? diff --git a/app/models/issue.rb b/app/models/issue.rb index 0332bfa9371..41a290f34b4 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -14,12 +14,13 @@ class Issue < ActiveRecord::Base ignore_column :assignee_id, :branch_name, :deleted_at - DueDateStruct = Struct.new(:title, :name).freeze - NoDueDate = DueDateStruct.new('No Due Date', '0').freeze - AnyDueDate = DueDateStruct.new('Any Due Date', '').freeze - Overdue = DueDateStruct.new('Overdue', 'overdue').freeze - DueThisWeek = DueDateStruct.new('Due This Week', 'week').freeze - DueThisMonth = DueDateStruct.new('Due This Month', 'month').freeze + DueDateStruct = Struct.new(:title, :name).freeze + NoDueDate = DueDateStruct.new('No Due Date', '0').freeze + AnyDueDate = DueDateStruct.new('Any Due Date', '').freeze + Overdue = DueDateStruct.new('Overdue', 'overdue').freeze + DueThisWeek = DueDateStruct.new('Due This Week', 'week').freeze + DueThisMonth = DueDateStruct.new('Due This Month', 'month').freeze + DueNextMonthAndPreviousTwoWeeks = DueDateStruct.new('Due Next Month And Previous Two Weeks', 'next_month_and_previous_two_weeks').freeze belongs_to :project belongs_to :moved_to, class_name: 'Issue' @@ -46,6 +47,7 @@ class Issue < ActiveRecord::Base scope :unassigned, -> { where('NOT EXISTS (SELECT TRUE FROM issue_assignees WHERE issue_id = issues.id)') } scope :assigned_to, ->(u) { where('EXISTS (SELECT TRUE FROM issue_assignees WHERE user_id = ? AND issue_id = issues.id)', u.id)} + scope :with_due_date, -> { where('due_date IS NOT NULL') } scope :without_due_date, -> { where(due_date: nil) } scope :due_before, ->(date) { where('issues.due_date < ?', date) } scope :due_between, ->(from_date, to_date) { where('issues.due_date >= ?', from_date).where('issues.due_date <= ?', to_date) } @@ -53,12 +55,14 @@ class Issue < ActiveRecord::Base scope :order_due_date_asc, -> { reorder('issues.due_date IS NULL, issues.due_date ASC') } scope :order_due_date_desc, -> { reorder('issues.due_date IS NULL, issues.due_date DESC') } + scope :order_closest_future_date, -> { reorder('CASE WHEN due_date >= CURRENT_DATE THEN 0 ELSE 1 END ASC, ABS(CURRENT_DATE - due_date) ASC') } scope :preload_associations, -> { preload(:labels, project: :namespace) } scope :public_only, -> { where(confidential: false) } after_save :expire_etag_cache + after_save :ensure_metrics, unless: :imported? attr_spammable :title, spam_title: true attr_spammable :description, spam_description: true @@ -119,6 +123,7 @@ class Issue < ActiveRecord::Base def self.sort_by_attribute(method, excluded_labels: []) case method.to_s + when 'closest_future_date' then order_closest_future_date when 'due_date' then order_due_date_asc when 'due_date_asc' then order_due_date_asc when 'due_date_desc' then order_due_date_desc diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index bc97fc3a5d9..79fc155fd3c 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -58,6 +58,7 @@ class MergeRequest < ActiveRecord::Base after_create :ensure_merge_request_diff, unless: :importing? after_update :clear_memoized_shas after_update :reload_diff_if_branch_changed + after_save :ensure_metrics # When this attribute is true some MR validation is ignored # It allows us to close or modify broken merge requests diff --git a/app/models/user.rb b/app/models/user.rb index 0a838d34054..e219ab800ad 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -26,7 +26,7 @@ class User < ActiveRecord::Base ignore_column :authentication_token add_authentication_token_field :incoming_email_token - add_authentication_token_field :rss_token + add_authentication_token_field :feed_token default_value_for :admin, false default_value_for(:external) { Gitlab::CurrentSettings.user_default_external } @@ -1167,11 +1167,11 @@ class User < ActiveRecord::Base save end - # each existing user needs to have an `rss_token`. + # each existing user needs to have an `feed_token`. # we do this on read since migrating all existing users is not a feasible # solution. - def rss_token - ensure_rss_token! + def feed_token + ensure_feed_token! end def sync_attribute?(attribute) diff --git a/app/presenters/commit_status_presenter.rb b/app/presenters/commit_status_presenter.rb index c7f7aa836bd..9a7aaf4ef32 100644 --- a/app/presenters/commit_status_presenter.rb +++ b/app/presenters/commit_status_presenter.rb @@ -1,11 +1,10 @@ class CommitStatusPresenter < Gitlab::View::Presenter::Delegated CALLOUT_FAILURE_MESSAGES = { unknown_failure: 'There is an unknown failure, please try again', - script_failure: 'There has been a script failure. Check the job log for more information', api_failure: 'There has been an API failure, please try again', stuck_or_timeout_failure: 'There has been a timeout failure or the job got stuck. Check your timeout limits or try again', runner_system_failure: 'There has been a runner system failure, please try again', - missing_dependency_failure: 'There has been a missing dependency failure, check the job log for more information' + missing_dependency_failure: 'There has been a missing dependency failure' }.freeze presents :build diff --git a/app/serializers/job_entity.rb b/app/serializers/job_entity.rb index 3076fed1674..960e7291ae6 100644 --- a/app/serializers/job_entity.rb +++ b/app/serializers/job_entity.rb @@ -26,7 +26,7 @@ class JobEntity < Grape::Entity expose :created_at expose :updated_at expose :detailed_status, as: :status, with: StatusEntity - expose :callout_message, if: -> (*) { failed? } + expose :callout_message, if: -> (*) { failed? && !build.script_failure? } expose :recoverable, if: -> (*) { failed? } private diff --git a/app/services/boards/issues/list_service.rb b/app/services/boards/issues/list_service.rb index ac70a99c2c5..5a961ac89e4 100644 --- a/app/services/boards/issues/list_service.rb +++ b/app/services/boards/issues/list_service.rb @@ -63,7 +63,7 @@ module Boards def without_board_labels(issues) return issues unless board_label_ids.any? - issues.where.not(issues_label_links.limit(1).arel.exists) + issues.where.not('EXISTS (?)', issues_label_links.limit(1)) end def issues_label_links @@ -71,10 +71,8 @@ module Boards end def with_list_label(issues) - issues.where( - LabelLink.where("label_links.target_type = 'Issue' AND label_links.target_id = issues.id") - .where("label_links.label_id = ?", list.label_id).limit(1).arel.exists - ) + issues.where('EXISTS (?)', LabelLink.where("label_links.target_type = 'Issue' AND label_links.target_id = issues.id") + .where("label_links.label_id = ?", list.label_id).limit(1)) end end end diff --git a/app/views/dashboard/issues.html.haml b/app/views/dashboard/issues.html.haml index 4bf04dadf01..86a21e24ac9 100644 --- a/app/views/dashboard/issues.html.haml +++ b/app/views/dashboard/issues.html.haml @@ -7,8 +7,7 @@ .top-area = render 'shared/issuable/nav', type: :issues, display_count: !@no_filters_set .nav-controls - = link_to safe_params.merge(rss_url_options), class: 'btn has-tooltip', data: { container: 'body' }, title: 'Subscribe' do - = icon('rss') + = render 'shared/issuable/feed_buttons' = render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", with_feature_enabled: 'issues', type: :issues = render 'shared/issuable/filter', type: :issues diff --git a/app/views/dashboard/issues_calendar.ics.haml b/app/views/dashboard/issues_calendar.ics.haml new file mode 100644 index 00000000000..59573e5fecf --- /dev/null +++ b/app/views/dashboard/issues_calendar.ics.haml @@ -0,0 +1 @@ += render 'issues/issues_calendar', issues: @issues diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml index 662db18cf86..8037cf4b69d 100644 --- a/app/views/groups/issues.html.haml +++ b/app/views/groups/issues.html.haml @@ -8,10 +8,7 @@ .top-area = render 'shared/issuable/nav', type: :issues .nav-controls - = link_to safe_params.merge(rss_url_options), class: 'btn' do - = icon('rss') - %span.icon-label - Subscribe + = render 'shared/issuable/feed_buttons' = render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", type: :issues = render 'shared/issuable/search_bar', type: :issues diff --git a/app/views/groups/issues_calendar.ics.haml b/app/views/groups/issues_calendar.ics.haml new file mode 100644 index 00000000000..59573e5fecf --- /dev/null +++ b/app/views/groups/issues_calendar.ics.haml @@ -0,0 +1 @@ += render 'issues/issues_calendar', issues: @issues diff --git a/app/views/issues/_issues_calendar.ics.ruby b/app/views/issues/_issues_calendar.ics.ruby new file mode 100644 index 00000000000..3563635d33d --- /dev/null +++ b/app/views/issues/_issues_calendar.ics.ruby @@ -0,0 +1,15 @@ +cal = Icalendar::Calendar.new +cal.prodid = '-//GitLab//NONSGML GitLab//EN' +cal.x_wr_calname = 'GitLab Issues' + +@issues.includes(project: :namespace).each do |issue| + cal.event do |event| + event.dtstart = Icalendar::Values::Date.new(issue.due_date) + event.summary = "#{issue.title} (in #{issue.project.full_path})" + event.description = "Find out more at #{issue_url(issue)}" + event.url = issue_url(issue) + event.transp = 'TRANSPARENT' + end +end + +cal.to_ical diff --git a/app/views/profiles/personal_access_tokens/index.html.haml b/app/views/profiles/personal_access_tokens/index.html.haml index d253e8e456e..d111113c646 100644 --- a/app/views/profiles/personal_access_tokens/index.html.haml +++ b/app/views/profiles/personal_access_tokens/index.html.haml @@ -34,18 +34,18 @@ .row.prepend-top-default .col-lg-4.profile-settings-sidebar %h4.prepend-top-0 - RSS token + Feed token %p - Your RSS token is used to authenticate you when your RSS reader loads a personalized RSS feed, and is included in your personal RSS feed URLs. + Your feed token is used to authenticate you when your RSS reader loads a personalized RSS feed or when when your calendar application loads a personalized calendar, and is included in those feed URLs. %p It cannot be used to access any other data. - .col-lg-8.rss-token-reset - = label_tag :rss_token, 'RSS token', class: "label-light" - = text_field_tag :rss_token, current_user.rss_token, class: 'form-control', readonly: true, onclick: 'this.select()' + .col-lg-8.feed-token-reset + = label_tag :feed_token, 'Feed token', class: "label-light" + = text_field_tag :feed_token, current_user.feed_token, class: 'form-control', readonly: true, onclick: 'this.select()' %p.form-text.text-muted - Keep this token secret. Anyone who gets ahold of it can read activity and issue RSS feeds as if they were you. + Keep this token secret. Anyone who gets ahold of it can read activity and issue RSS feeds or your calendar feed as if they were you. You should - = link_to 'reset it', [:reset, :rss_token, :profile], method: :put, data: { confirm: 'Are you sure? Any RSS URLs currently in use will stop working.' } + = link_to 'reset it', [:reset, :feed_token, :profile], method: :put, data: { confirm: 'Are you sure? Any RSS or calendar URLs currently in use will stop working.' } if that ever happens. - if incoming_email_token_enabled? diff --git a/app/views/projects/issues/_nav_btns.html.haml b/app/views/projects/issues/_nav_btns.html.haml index 297b928f020..0dd2d2e6c5d 100644 --- a/app/views/projects/issues/_nav_btns.html.haml +++ b/app/views/projects/issues/_nav_btns.html.haml @@ -1,5 +1,5 @@ -= link_to safe_params.merge(rss_url_options), class: 'btn btn-default append-right-10 has-tooltip', title: 'Subscribe' do - = icon('rss') += render 'shared/issuable/feed_buttons' + - if @can_bulk_update = button_tag "Edit issues", class: "btn btn-default append-right-10 js-bulk-update-toggle" - if show_new_issue_link?(@project) diff --git a/app/views/projects/issues/calendar.ics.haml b/app/views/projects/issues/calendar.ics.haml new file mode 100644 index 00000000000..59573e5fecf --- /dev/null +++ b/app/views/projects/issues/calendar.ics.haml @@ -0,0 +1 @@ += render 'issues/issues_calendar', issues: @issues diff --git a/app/views/shared/icons/_icon_calendar.svg b/app/views/shared/icons/_icon_calendar.svg new file mode 100644 index 00000000000..4d0a703f9a0 --- /dev/null +++ b/app/views/shared/icons/_icon_calendar.svg @@ -0,0 +1 @@ +<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M15 5v7a3 3 0 0 1-3 3H4a3 3 0 0 1-3-3V5a2 2 0 0 1 2-2h1V2a1 1 0 1 1 2 0v1h4V2a1 1 0 1 1 2 0v1h1a2 2 0 0 1 2 2zM3 6v6a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V6H3zm2 2h2a1 1 0 1 1 0 2H5a1 1 0 1 1 0-2z" fill="#000" fill-rule="evenodd"/></svg>
\ No newline at end of file diff --git a/app/views/shared/issuable/_feed_buttons.html.haml b/app/views/shared/issuable/_feed_buttons.html.haml new file mode 100644 index 00000000000..d4834090413 --- /dev/null +++ b/app/views/shared/issuable/_feed_buttons.html.haml @@ -0,0 +1,4 @@ += link_to safe_params.merge(rss_url_options), class: 'btn has-tooltip', data: { container: 'body' }, title: 'Subscribe to RSS feed' do + = icon('rss') += link_to safe_params.merge(calendar_url_options), class: 'btn has-tooltip', data: { container: 'body' }, title: 'Subscribe to calendar' do + = custom_icon('icon_calendar') diff --git a/app/views/shared/milestones/_tabs.html.haml b/app/views/shared/milestones/_tabs.html.haml index e6a65161ed6..55460acab8f 100644 --- a/app/views/shared/milestones/_tabs.html.haml +++ b/app/views/shared/milestones/_tabs.html.haml @@ -5,25 +5,25 @@ .fade-right= icon('angle-right') %ul.nav-links.scrolling-tabs.js-milestone-tabs.nav.nav-tabs - if issues_accessible - %li.active - = link_to '#tab-issues', 'data-toggle' => 'tab', 'data-show' => '.tab-issues-buttons' do + %li.nav-item + = link_to '#tab-issues', class: 'nav-link active', 'data-toggle' => 'tab', 'data-show' => '.tab-issues-buttons' do Issues %span.badge.badge-pill= milestone.issues_visible_to_user(current_user).size - %li - = link_to '#tab-merge-requests', 'data-toggle' => 'tab', 'data-endpoint': milestone_merge_request_tab_path(milestone) do + %li.nav-item + = link_to '#tab-merge-requests', class: 'nav-link', 'data-toggle' => 'tab', 'data-endpoint': milestone_merge_request_tab_path(milestone) do Merge Requests %span.badge.badge-pill= milestone.merge_requests.size - else - %li.active - = link_to '#tab-merge-requests', 'data-toggle' => 'tab', 'data-endpoint': milestone_merge_request_tab_path(milestone) do + %li.nav-item + = link_to '#tab-merge-requests', class: 'nav-link active', 'data-toggle' => 'tab', 'data-endpoint': milestone_merge_request_tab_path(milestone) do Merge Requests %span.badge.badge-pill= milestone.merge_requests.size - %li - = link_to '#tab-participants', 'data-toggle' => 'tab', 'data-endpoint': milestone_participants_tab_path(milestone) do + %li.nav-item + = link_to '#tab-participants', class: 'nav-link', 'data-toggle' => 'tab', 'data-endpoint': milestone_participants_tab_path(milestone) do Participants %span.badge.badge-pill= milestone.participants.count - %li - = link_to '#tab-labels', 'data-toggle' => 'tab', 'data-endpoint': milestone_labels_tab_path(milestone) do + %li.nav-item + = link_to '#tab-labels', class: 'nav-link', 'data-toggle' => 'tab', 'data-endpoint': milestone_labels_tab_path(milestone) do Labels %span.badge.badge-pill= milestone.labels.count |