Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-01-21 03:11:07 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-01-21 03:11:07 +0300
commit9f021745a60369298705393103f621ff73a2b286 (patch)
treea6e9f85a8f35245e4ccef5f532c6ca6857f08d9e /app
parentef8c47e97e1c178291e4857314a3f53875d75062 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/design_management/components/list/item.vue2
-rw-r--r--app/assets/javascripts/notes/stores/mutations.js14
-rw-r--r--app/assets/javascripts/search/topbar/components/project_filter.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/suggestions.vue12
-rw-r--r--app/assets/stylesheets/components/design_management/design_list_item.scss5
-rw-r--r--app/controllers/concerns/integrations_actions.rb4
-rw-r--r--app/finders/terraform/states_finder.rb28
-rw-r--r--app/graphql/mutations/security/ci_configuration/configure_sast.rb50
-rw-r--r--app/graphql/resolvers/terraform/states_resolver.rb18
-rw-r--r--app/graphql/types/mutation_type.rb1
-rw-r--r--app/graphql/types/project_type.rb8
-rw-r--r--app/helpers/groups_helper.rb23
-rw-r--r--app/models/terraform/state.rb1
-rw-r--r--app/services/groups/open_issues_count_service.rb64
-rw-r--r--app/services/security/ci_configuration/sast_create_service.rb66
-rw-r--r--app/views/groups/_import_group_from_another_instance_panel.html.haml13
-rw-r--r--app/views/groups/runners/_runner.html.haml5
-rw-r--r--app/views/layouts/nav/sidebar/_group.html.haml6
-rw-r--r--app/views/projects/branches/_branch.html.haml16
-rw-r--r--app/views/projects/buttons/_fork.html.haml10
-rw-r--r--app/views/projects/notes/_actions.html.haml14
-rw-r--r--app/views/projects/notes/_more_actions_dropdown.html.haml7
-rw-r--r--app/views/projects/pages/_use.html.haml7
-rw-r--r--app/views/shared/access_tokens/_form.html.haml2
-rw-r--r--app/views/shared/access_tokens/_table.html.haml2
25 files changed, 321 insertions, 59 deletions
diff --git a/app/assets/javascripts/design_management/components/list/item.vue b/app/assets/javascripts/design_management/components/list/item.vue
index 5402fed0a9a..67110265b5f 100644
--- a/app/assets/javascripts/design_management/components/list/item.vue
+++ b/app/assets/javascripts/design_management/components/list/item.vue
@@ -133,7 +133,7 @@ export default {
<div
class="card-body gl-p-0 gl-display-flex gl-align-items-center gl-justify-content-center gl-overflow-hidden gl-relative"
>
- <div v-if="icon.name" data-testid="design-event" class="design-event gl-absolute">
+ <div v-if="icon.name" data-testid="design-event" class="gl-top-5 gl-right-5 gl-absolute">
<span :title="icon.tooltip" :aria-label="icon.tooltip">
<gl-icon
:name="icon.name"
diff --git a/app/assets/javascripts/notes/stores/mutations.js b/app/assets/javascripts/notes/stores/mutations.js
index 2c51ce0d970..1a3c15bedb8 100644
--- a/app/assets/javascripts/notes/stores/mutations.js
+++ b/app/assets/javascripts/notes/stores/mutations.js
@@ -1,3 +1,4 @@
+import { isEqual } from 'lodash';
import * as utils from './utils';
import * as types from './mutation_types';
import * as constants from '../constants';
@@ -31,7 +32,8 @@ export default {
}
}
- note.base_discussion = undefined; // No point keeping a reference to this
+ // note.base_discussion = undefined; // No point keeping a reference to this
+ delete note.base_discussion;
discussion.notes = [note];
state.discussions.push(discussion);
@@ -220,6 +222,11 @@ export default {
[types.UPDATE_NOTE](state, note) {
const noteObj = utils.findNoteObjectById(state.discussions, note.discussion_id);
+ // Disable eslint here so we can delete the property that we no longer need
+ // in the note object
+ // eslint-disable-next-line no-param-reassign
+ delete note.base_discussion;
+
if (noteObj.individual_note) {
if (note.type === constants.DISCUSSION_NOTE) {
noteObj.individual_note = false;
@@ -228,7 +235,10 @@ export default {
noteObj.notes.splice(0, 1, note);
} else {
const comment = utils.findNoteObjectById(noteObj.notes, note.id);
- noteObj.notes.splice(noteObj.notes.indexOf(comment), 1, note);
+
+ if (!isEqual(comment, note)) {
+ noteObj.notes.splice(noteObj.notes.indexOf(comment), 1, note);
+ }
}
},
diff --git a/app/assets/javascripts/search/topbar/components/project_filter.vue b/app/assets/javascripts/search/topbar/components/project_filter.vue
index 3f1f3848ac7..effaafb72c1 100644
--- a/app/assets/javascripts/search/topbar/components/project_filter.vue
+++ b/app/assets/javascripts/search/topbar/components/project_filter.vue
@@ -27,7 +27,7 @@ export default {
handleProjectChange(project) {
// This determines if we need to update the group filter or not
const queryParams = {
- ...(project.namespace_id && { [GROUP_DATA.queryParam]: project.namespace_id }),
+ ...(project.namespace?.id && { [GROUP_DATA.queryParam]: project.namespace.id }),
[PROJECT_DATA.queryParam]: project.id,
};
diff --git a/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue b/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue
index 5ee51764555..94a216596a8 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/suggestions.vue
@@ -64,6 +64,11 @@ export default {
mounted() {
this.renderSuggestions();
},
+ beforeDestroy() {
+ if (this.suggestionsWatch) {
+ this.suggestionsWatch();
+ }
+ },
methods: {
renderSuggestions() {
// swaps out suggestion(s) markdown with rich diff components
@@ -108,6 +113,13 @@ export default {
},
});
+ // We're using `$watch` as `suggestionsCount` updates do not
+ // propagate to this component for some unknown reason while
+ // using a traditional prop watcher.
+ this.suggestionsWatch = this.$watch('suggestionsCount', () => {
+ suggestionDiff.suggestionsCount = this.suggestionsCount;
+ });
+
suggestionDiff.$on('apply', ({ suggestionId, callback, message }) => {
this.$emit('apply', { suggestionId, callback, flashContainer: this.$el, message });
});
diff --git a/app/assets/stylesheets/components/design_management/design_list_item.scss b/app/assets/stylesheets/components/design_management/design_list_item.scss
index b7f6b2026fe..09af4da37e9 100644
--- a/app/assets/stylesheets/components/design_management/design_list_item.scss
+++ b/app/assets/stylesheets/components/design_management/design_list_item.scss
@@ -8,11 +8,6 @@
top: 10px;
}
- .design-event {
- top: $gl-padding;
- right: $gl-padding;
- }
-
.card-body {
height: 230px;
}
diff --git a/app/controllers/concerns/integrations_actions.rb b/app/controllers/concerns/integrations_actions.rb
index baebedb8e5d..a3ea39d9c3d 100644
--- a/app/controllers/concerns/integrations_actions.rb
+++ b/app/controllers/concerns/integrations_actions.rb
@@ -34,10 +34,6 @@ module IntegrationsActions
end
end
- def custom_integration_projects
- Project.with_custom_integration_compared_to(integration).page(params[:page]).per(20)
- end
-
def test
render json: {}, status: :ok
end
diff --git a/app/finders/terraform/states_finder.rb b/app/finders/terraform/states_finder.rb
new file mode 100644
index 00000000000..bbe90fead2b
--- /dev/null
+++ b/app/finders/terraform/states_finder.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+module Terraform
+ class StatesFinder
+ def initialize(project, current_user, params: {})
+ @project = project
+ @current_user = current_user
+ @params = params
+ end
+
+ def execute
+ return ::Terraform::State.none unless can_read_terraform_states?
+
+ states = project.terraform_states
+ states = states.with_name(params[:name]) if params[:name].present?
+
+ states.ordered_by_name
+ end
+
+ private
+
+ attr_reader :project, :current_user, :params
+
+ def can_read_terraform_states?
+ current_user.can?(:read_terraform_state, project)
+ end
+ end
+end
diff --git a/app/graphql/mutations/security/ci_configuration/configure_sast.rb b/app/graphql/mutations/security/ci_configuration/configure_sast.rb
new file mode 100644
index 00000000000..6cb3704a19b
--- /dev/null
+++ b/app/graphql/mutations/security/ci_configuration/configure_sast.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Security
+ module CiConfiguration
+ class ConfigureSast < BaseMutation
+ include ResolvesProject
+
+ graphql_name 'ConfigureSast'
+
+ argument :project_path, GraphQL::ID_TYPE,
+ required: true,
+ description: 'Full path of the project.'
+
+ argument :configuration, ::Types::CiConfiguration::Sast::InputType,
+ required: true,
+ description: 'SAST CI configuration for the project.'
+
+ field :status, GraphQL::STRING_TYPE, null: false,
+ description: 'Status of creating the commit for the supplied SAST CI configuration.'
+
+ field :success_path, GraphQL::STRING_TYPE, null: true,
+ description: 'Redirect path to use when the response is successful.'
+
+ authorize :push_code
+
+ def resolve(project_path:, configuration:)
+ project = authorized_find!(full_path: project_path)
+
+ result = ::Security::CiConfiguration::SastCreateService.new(project, current_user, configuration).execute
+ prepare_response(result)
+ end
+
+ private
+
+ def find_object(full_path:)
+ resolve_project(full_path: full_path)
+ end
+
+ def prepare_response(result)
+ {
+ status: result[:status],
+ success_path: result[:success_path],
+ errors: Array(result[:errors])
+ }
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/resolvers/terraform/states_resolver.rb b/app/graphql/resolvers/terraform/states_resolver.rb
index 38b26a948b1..f543eb182e8 100644
--- a/app/graphql/resolvers/terraform/states_resolver.rb
+++ b/app/graphql/resolvers/terraform/states_resolver.rb
@@ -3,20 +3,20 @@
module Resolvers
module Terraform
class StatesResolver < BaseResolver
- type Types::Terraform::StateType, null: true
+ type Types::Terraform::StateType.connection_type, null: true
alias_method :project, :object
- def resolve(**args)
- return ::Terraform::State.none unless can_read_terraform_states?
-
- project.terraform_states.ordered_by_name
+ when_single do
+ argument :name, GraphQL::STRING_TYPE,
+ required: true,
+ description: 'Name of the Terraform state.'
end
- private
-
- def can_read_terraform_states?
- current_user.can?(:read_terraform_state, project)
+ def resolve(**args)
+ ::Terraform::StatesFinder
+ .new(project, current_user, params: args)
+ .execute
end
end
end
diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb
index f9dd11cbe37..11980e009f4 100644
--- a/app/graphql/types/mutation_type.rb
+++ b/app/graphql/types/mutation_type.rb
@@ -15,6 +15,7 @@ module Types
mount_mutation Mutations::AlertManagement::HttpIntegration::Update
mount_mutation Mutations::AlertManagement::HttpIntegration::ResetToken
mount_mutation Mutations::AlertManagement::HttpIntegration::Destroy
+ mount_mutation Mutations::Security::CiConfiguration::ConfigureSast
mount_mutation Mutations::AlertManagement::PrometheusIntegration::Create
mount_mutation Mutations::AlertManagement::PrometheusIntegration::Update
mount_mutation Mutations::AlertManagement::PrometheusIntegration::ResetToken
diff --git a/app/graphql/types/project_type.rb b/app/graphql/types/project_type.rb
index 41a4edd20a5..257c77e2ede 100644
--- a/app/graphql/types/project_type.rb
+++ b/app/graphql/types/project_type.rb
@@ -309,10 +309,16 @@ module Types
description: 'Title of the label'
end
+ field :terraform_state,
+ Types::Terraform::StateType,
+ null: true,
+ description: 'Find a single Terraform state by name.',
+ resolver: Resolvers::Terraform::StatesResolver.single
+
field :terraform_states,
Types::Terraform::StateType.connection_type,
null: true,
- description: 'Terraform states associated with the project',
+ description: 'Terraform states associated with the project.',
resolver: Resolvers::Terraform::StatesResolver
field :pipeline_analytics, Types::Ci::AnalyticsType, null: true,
diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb
index 133d9d21a14..eeeffb7b3ae 100644
--- a/app/helpers/groups_helper.rb
+++ b/app/helpers/groups_helper.rb
@@ -62,6 +62,14 @@ module GroupsHelper
can?(current_user, :set_emails_disabled, group) && !group.parent&.emails_disabled?
end
+ def group_open_issues_count(group)
+ if Feature.enabled?(:cached_sidebar_open_issues_count, group)
+ cached_open_group_issues_count(group)
+ else
+ number_with_delimiter(group_issues_count(state: 'opened'))
+ end
+ end
+
def group_issues_count(state:)
IssuesFinder
.new(current_user, group_id: @group.id, state: state, non_archived: true, include_subgroups: true)
@@ -69,6 +77,21 @@ module GroupsHelper
.count
end
+ def cached_open_group_issues_count(group)
+ count_service = Groups::OpenIssuesCountService
+ issues_count = count_service.new(group, current_user).count
+
+ if issues_count > count_service::CACHED_COUNT_THRESHOLD
+ ActiveSupport::NumberHelper
+ .number_to_human(
+ issues_count,
+ units: { thousand: 'k', million: 'm' }, precision: 1, significant: false, format: '%n%u'
+ )
+ else
+ number_with_delimiter(issues_count)
+ end
+ end
+
def group_merge_requests_count(state:)
MergeRequestsFinder
.new(current_user, group_id: @group.id, state: state, non_archived: true, include_subgroups: true)
diff --git a/app/models/terraform/state.rb b/app/models/terraform/state.rb
index efbbd86ae4a..a301c4a9689 100644
--- a/app/models/terraform/state.rb
+++ b/app/models/terraform/state.rb
@@ -22,6 +22,7 @@ module Terraform
scope :versioning_not_enabled, -> { where(versioning_enabled: false) }
scope :ordered_by_name, -> { order(:name) }
+ scope :with_name, -> (name) { where(name: name) }
validates :project_id, presence: true
validates :uuid, presence: true, uniqueness: true, length: { is: UUID_LENGTH },
diff --git a/app/services/groups/open_issues_count_service.rb b/app/services/groups/open_issues_count_service.rb
new file mode 100644
index 00000000000..db1ca09212a
--- /dev/null
+++ b/app/services/groups/open_issues_count_service.rb
@@ -0,0 +1,64 @@
+# frozen_string_literal: true
+
+module Groups
+ # Service class for counting and caching the number of open issues of a group.
+ class OpenIssuesCountService < BaseCountService
+ include Gitlab::Utils::StrongMemoize
+
+ VERSION = 1
+ PUBLIC_COUNT_KEY = 'group_public_open_issues_count'
+ TOTAL_COUNT_KEY = 'group_total_open_issues_count'
+ CACHED_COUNT_THRESHOLD = 1000
+ EXPIRATION_TIME = 24.hours
+
+ attr_reader :group, :user
+
+ def initialize(group, user = nil)
+ @group = group
+ @user = user
+ end
+
+ # Reads count value from cache and return it if present.
+ # If empty or expired, #uncached_count will calculate the issues count for the group and
+ # compare it with the threshold. If it is greater, it will be written to the cache and returned.
+ # If below, it will be returned without being cached.
+ # This results in only caching large counts and calculating the rest with every call to maintain
+ # accuracy.
+ def count
+ cached_count = Rails.cache.read(cache_key)
+ return cached_count unless cached_count.blank?
+
+ refreshed_count = uncached_count
+ update_cache_for_key(cache_key) { refreshed_count } if refreshed_count > CACHED_COUNT_THRESHOLD
+ refreshed_count
+ end
+
+ def cache_key(key = nil)
+ ['groups', 'open_issues_count_service', VERSION, group.id, cache_key_name]
+ end
+
+ private
+
+ def cache_options
+ super.merge({ expires_in: EXPIRATION_TIME })
+ end
+
+ def cache_key_name
+ public_only? ? PUBLIC_COUNT_KEY : TOTAL_COUNT_KEY
+ end
+
+ def public_only?
+ !user_is_at_least_reporter?
+ end
+
+ def user_is_at_least_reporter?
+ strong_memoize(:user_is_at_least_reporter) do
+ group.member?(user, Gitlab::Access::REPORTER)
+ end
+ end
+
+ def relation_for_count
+ IssuesFinder.new(user, group_id: group.id, state: 'opened', non_archived: true, include_subgroups: true, public_only: public_only?).execute
+ end
+ end
+end
diff --git a/app/services/security/ci_configuration/sast_create_service.rb b/app/services/security/ci_configuration/sast_create_service.rb
new file mode 100644
index 00000000000..8fc3b8d078c
--- /dev/null
+++ b/app/services/security/ci_configuration/sast_create_service.rb
@@ -0,0 +1,66 @@
+# frozen_string_literal: true
+
+module Security
+ module CiConfiguration
+ class SastCreateService < ::BaseService
+ def initialize(project, current_user, params)
+ @project = project
+ @current_user = current_user
+ @params = params
+ @branch_name = @project.repository.next_branch('set-sast-config')
+ end
+
+ def execute
+ attributes_for_commit = attributes
+ result = ::Files::MultiService.new(@project, @current_user, attributes_for_commit).execute
+
+ if result[:status] == :success
+ result[:success_path] = successful_change_path
+ track_event(attributes_for_commit)
+ else
+ result[:errors] = result[:message]
+ end
+
+ result
+
+ rescue Gitlab::Git::PreReceiveError => e
+ { status: :error, errors: e.message }
+ end
+
+ private
+
+ def attributes
+ actions = Security::CiConfiguration::SastBuildActions.new(@project.auto_devops_enabled?, @params, existing_gitlab_ci_content).generate
+
+ @project.repository.add_branch(@current_user, @branch_name, @project.default_branch)
+ message = _('Set .gitlab-ci.yml to enable or configure SAST')
+
+ {
+ commit_message: message,
+ branch_name: @branch_name,
+ start_branch: @branch_name,
+ actions: actions
+ }
+ end
+
+ def existing_gitlab_ci_content
+ gitlab_ci_yml = @project.repository.gitlab_ci_yml_for(@project.repository.root_ref_sha)
+ YAML.safe_load(gitlab_ci_yml) if gitlab_ci_yml
+ end
+
+ def successful_change_path
+ description = _('Set .gitlab-ci.yml to enable or configure SAST security scanning using the GitLab managed template. You can [add variable overrides](https://docs.gitlab.com/ee/user/application_security/sast/#customizing-the-sast-settings) to customize SAST settings.')
+ merge_request_params = { source_branch: @branch_name, description: description }
+ Gitlab::Routing.url_helpers.project_new_merge_request_url(@project, merge_request: merge_request_params)
+ end
+
+ def track_event(attributes_for_commit)
+ action = attributes_for_commit[:actions].first
+
+ Gitlab::Tracking.event(
+ self.class.to_s, action[:action], label: action[:default_values_overwritten].to_s
+ )
+ end
+ end
+ end
+end
diff --git a/app/views/groups/_import_group_from_another_instance_panel.html.haml b/app/views/groups/_import_group_from_another_instance_panel.html.haml
index c95e7c16161..83d2e13d345 100644
--- a/app/views/groups/_import_group_from_another_instance_panel.html.haml
+++ b/app/views/groups/_import_group_from_another_instance_panel.html.haml
@@ -2,9 +2,18 @@
= form_errors(@group)
.gl-border-l-solid.gl-border-r-solid.gl-border-gray-100.gl-border-1.gl-p-5
- %h4
+ %h4.gl-display-flex
= s_('GroupsNew|Import groups from another instance of GitLab')
- %p
+ %span.badge.badge-info.badge-pill.gl-badge.md.gl-ml-3
+ = _('Beta')
+ .gl-alert.gl-alert-warning{ role: 'alert' }
+ = sprite_icon('warning', css_class: 'gl-icon s16 gl-alert-icon gl-alert-icon-no-title')
+ .gl-alert-body
+ - docs_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: help_page_path('user/group/import/index.md') }
+ - feedback_link_start = '<a href="https://gitlab.com/gitlab-org/gitlab/-/issues/284495" target="_blank" rel="noopener noreferrer">'.html_safe
+ - link_end = '</a>'.html_safe
+ = s_('GroupsNew|Not all related objects are migrated, as %{docs_link_start}described here%{docs_link_end}. Please %{feedback_link_start}leave feedback%{feedback_link_end} on this feature.').html_safe % { docs_link_start: docs_link_start, docs_link_end: link_end, feedback_link_start: feedback_link_start, feedback_link_end: link_end }
+ %p.gl-mt-3
= s_('GroupsNew|Provide credentials for another instance of GitLab to import your groups directly.')
.form-group.gl-display-flex.gl-flex-direction-column
= f.label :bulk_import_gitlab_url, s_('GroupsNew|GitLab source URL'), for: 'import_gitlab_url'
diff --git a/app/views/groups/runners/_runner.html.haml b/app/views/groups/runners/_runner.html.haml
index 3fc50cc86d2..80739395713 100644
--- a/app/views/groups/runners/_runner.html.haml
+++ b/app/views/groups/runners/_runner.html.haml
@@ -77,8 +77,9 @@
= link_to resume_group_runner_path(@group, runner), method: :post, class: 'btn btn-default has-tooltip', title: _('Resume'), ref: 'tooltip', aria: { label: _('Resume') }, data: { placement: 'top', container: 'body'} do
= sprite_icon('play')
- if runner.belongs_to_more_than_one_project?
- .btn-group
- .btn.btn-danger.has-tooltip{ 'aria-label' => 'Remove', 'data-container' => 'body', 'data-original-title' => _('Multi-project Runners cannot be removed'), 'data-placement' => 'top', disabled: 'disabled' }
+ - delete_runner_tooltip = _('Multi-project Runners cannot be removed')
+ .btn-group.has-tooltip{ data: { container: 'body', placement: 'top' }, title: delete_runner_tooltip }
+ .btn.btn-danger{ 'aria-label' => delete_runner_tooltip, disabled: 'disabled' }
= sprite_icon('close')
- else
.btn-group
diff --git a/app/views/layouts/nav/sidebar/_group.html.haml b/app/views/layouts/nav/sidebar/_group.html.haml
index 5569a69222f..52d3c8d133a 100644
--- a/app/views/layouts/nav/sidebar/_group.html.haml
+++ b/app/views/layouts/nav/sidebar/_group.html.haml
@@ -1,4 +1,4 @@
-- issues_count = group_issues_count(state: 'opened')
+- issues_count = group_open_issues_count(@group)
- merge_requests_count = group_merge_requests_count(state: 'opened')
.nav-sidebar{ class: ("sidebar-collapsed-desktop" if collapsed_sidebar?), **tracking_attrs('groups_side_navigation', 'render', 'groups_side_navigation') }
@@ -54,14 +54,14 @@
= sprite_icon('issues')
%span.nav-item-name
= _('Issues')
- %span.badge.badge-pill.count= number_with_delimiter(issues_count)
+ %span.badge.badge-pill.count= issues_count
%ul.sidebar-sub-level-items{ data: { qa_selector: 'group_issues_sidebar_submenu'} }
= nav_link(path: ['groups#issues', 'labels#index', 'milestones#index', 'iterations#index'], html_options: { class: "fly-out-top-item" } ) do
= link_to issues_group_path(@group) do
%strong.fly-out-top-item-name
= _('Issues')
- %span.badge.badge-pill.count.issue_counter.fly-out-badge= number_with_delimiter(issues_count)
+ %span.badge.badge-pill.count.issue_counter.fly-out-badge= issues_count
%li.divider.fly-out-top-item
= nav_link(path: 'groups#issues', html_options: { class: 'home' }) do
diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml
index dc4172e2f09..9ea4d3df631 100644
--- a/app/views/projects/branches/_branch.html.haml
+++ b/app/views/projects/branches/_branch.html.haml
@@ -50,10 +50,10 @@
- if can?(current_user, :push_code, @project)
- if branch.name == @project.repository.root_ref
- %button{ class: "gl-button btn btn-danger remove-row has-tooltip disabled",
- disabled: true,
- title: s_('Branches|The default branch cannot be deleted') }
- = sprite_icon("remove")
+ - delete_default_branch_tooltip = s_('Branches|The default branch cannot be deleted')
+ %span.has-tooltip{ title: delete_default_branch_tooltip }
+ %button{ class: "gl-button btn btn-danger remove-row disabled", disabled: true, 'aria-label' => delete_default_branch_tooltip }
+ = sprite_icon("remove")
- elsif protected_branch?(@project, branch)
- if can?(current_user, :push_to_delete_protected_branch, @project)
%button{ class: "gl-button btn btn-danger remove-row has-tooltip",
@@ -65,10 +65,10 @@
is_merged: ("true" if merged) } }
= sprite_icon("remove")
- else
- %button{ class: "gl-button btn btn-danger remove-row has-tooltip disabled",
- disabled: true,
- title: s_('Branches|Only a project maintainer or owner can delete a protected branch') }
- = sprite_icon("remove")
+ - delete_protected_branch_tooltip = s_('Branches|Only a project maintainer or owner can delete a protected branch')
+ %span.has-tooltip{ title: delete_protected_branch_tooltip }
+ %button{ class: "gl-button btn btn-danger remove-row disabled", disabled: true, 'aria-label' => delete_protected_branch_tooltip }
+ = sprite_icon("remove")
- else
= link_to project_branch_path(@project, branch.name),
class: "gl-button btn btn-danger remove-row qa-remove-btn js-ajax-loading-spinner has-tooltip",
diff --git a/app/views/projects/buttons/_fork.html.haml b/app/views/projects/buttons/_fork.html.haml
index dbe0bf35b98..fad168da71e 100644
--- a/app/views/projects/buttons/_fork.html.haml
+++ b/app/views/projects/buttons/_fork.html.haml
@@ -7,11 +7,11 @@
%span= s_('ProjectOverview|Fork')
- else
- can_create_fork = current_user.can?(:create_fork)
- = link_to new_project_fork_path(@project),
- class: "btn btn-default btn-xs has-tooltip count-badge-button d-flex align-items-center fork-btn #{'has-tooltip disabled' unless can_create_fork}",
- title: (s_('ProjectOverview|You have reached your project limit') unless can_create_fork) do
- = sprite_icon('fork', css_class: 'icon')
- %span= s_('ProjectOverview|Fork')
+ - disabled_fork_tooltip = s_('ProjectOverview|You have reached your project limit')
+ %span.has-tooltip{ title: (disabled_fork_tooltip unless can_create_fork) }
+ = link_to new_project_fork_path(@project), class: "btn btn-default btn-xs count-badge-button d-flex align-items-center fork-btn #{' disabled' unless can_create_fork }", 'aria-label' => (disabled_fork_tooltip unless can_create_fork) do
+ = sprite_icon('fork', css_class: 'icon')
+ %span= s_('ProjectOverview|Fork')
%span.fork-count.count-badge-count.d-flex.align-items-center
= link_to project_forks_path(@project), title: n_(s_('ProjectOverview|Fork'), s_('ProjectOverview|Forks'), @project.forks_count), class: 'count' do
= @project.forks_count
diff --git a/app/views/projects/notes/_actions.html.haml b/app/views/projects/notes/_actions.html.haml
index a785e36fad5..d11b61466e2 100644
--- a/app/views/projects/notes/_actions.html.haml
+++ b/app/views/projects/notes/_actions.html.haml
@@ -39,15 +39,15 @@
- if can?(current_user, :award_emoji, note)
- if note.emoji_awardable?
.note-actions-item
- = button_tag title: 'Add reaction', class: "note-action-button note-emoji-button js-add-award js-note-emoji has-tooltip btn btn-transparent", data: { position: 'right', container: 'body' } do
- %span{ class: 'link-highlight award-control-icon-neutral' }= sprite_icon('slight-smile')
- %span{ class: 'link-highlight award-control-icon-positive' }= sprite_icon('smiley')
- %span{ class: 'link-highlight award-control-icon-super-positive' }= sprite_icon('smile')
+ = button_tag title: 'Add reaction', class: "note-action-button note-emoji-button js-add-award js-note-emoji has-tooltip btn gl-button btn-icon btn-default-tertiary btn-transparent", data: { position: 'right', container: 'body' } do
+ = sprite_icon('slight-smile', css_class: 'link-highlight award-control-icon-neutral gl-button-icon gl-icon gl-text-gray-400')
+ = sprite_icon('smiley', css_class: 'link-highlight award-control-icon-positive gl-button-icon gl-icon gl-left-3!')
+ = sprite_icon('smile', css_class: 'link-highlight award-control-icon-super-positive gl-button-icon gl-icon gl-left-3! ')
- if note_editable
- .note-actions-item
- = button_tag title: 'Edit comment', class: 'note-action-button js-note-edit has-tooltip btn btn-transparent', data: { container: 'body', qa_selector: 'edit_comment_button' } do
+ .note-actions-item.gl-ml-0
+ = button_tag title: 'Edit comment', class: 'note-action-button js-note-edit has-tooltip btn gl-button btn-default-tertiary btn-transparent gl-px-2!', data: { container: 'body', qa_selector: 'edit_comment_button' } do
%span.link-highlight
- = custom_icon('icon_pencil')
+ = sprite_icon('pencil', css_class: 'gl-button-icon gl-icon gl-text-gray-400 s16')
= render 'projects/notes/more_actions_dropdown', note: note, note_editable: note_editable
diff --git a/app/views/projects/notes/_more_actions_dropdown.html.haml b/app/views/projects/notes/_more_actions_dropdown.html.haml
index 8cf1b6b9294..c81a3683e90 100644
--- a/app/views/projects/notes/_more_actions_dropdown.html.haml
+++ b/app/views/projects/notes/_more_actions_dropdown.html.haml
@@ -1,10 +1,9 @@
- is_current_user = current_user == note.author
- if note_editable || !is_current_user
- .dropdown.more-actions.note-actions-item
- = button_tag title: 'More actions', class: 'note-action-button more-actions-toggle has-tooltip btn btn-transparent', data: { toggle: 'dropdown', container: 'body', qa_selector: 'more_actions_dropdown' } do
- %span.icon
- = custom_icon('ellipsis_v')
+ %div{ class: "dropdown more-actions note-actions-item gl-ml-0!" }
+ = button_tag title: 'More actions', class: 'note-action-button more-actions-toggle has-tooltip btn gl-button btn-default-tertiary btn-transparent gl-pl-2! gl-pr-0!', data: { toggle: 'dropdown', container: 'body', qa_selector: 'more_actions_dropdown' } do
+ = sprite_icon('ellipsis_v', css_class: 'gl-button-icon gl-icon gl-text-gray-400')
%ul.dropdown-menu.more-actions-dropdown.dropdown-open-left
%li
= clipboard_button(text: noteable_note_url(note), title: _('Copy reference'), button_text: _('Copy link'), class: 'btn-clipboard', hide_tooltip: true, hide_button_icon: true)
diff --git a/app/views/projects/pages/_use.html.haml b/app/views/projects/pages/_use.html.haml
index e9ace8c72f1..ec3fc27dc20 100644
--- a/app/views/projects/pages/_use.html.haml
+++ b/app/views/projects/pages/_use.html.haml
@@ -4,7 +4,8 @@
= s_('GitLabPages|Configure pages')
.card-body
%p.gl-mb-0
- - link_start = "<a href='#{help_page_path('user/project/pages/index.md')}' target='_blank' rel='noopener noreferrer'>".html_safe
+ - docs_link_start = "<a href='#{help_page_path('user/project/pages/index.md')}' target='_blank' rel='noopener noreferrer'>".html_safe
+ - samples_link_start = "<a href='https://gitlab.com/pages' target='_blank' rel='noopener noreferrer'>".html_safe
+ - templates_link_start = "<a href='https://gitlab.com/gitlab-org/project-templates' target='_blank' rel='noopener noreferrer'>".html_safe
- link_end = '</a>'.html_safe
- = s_('GitLabPages|Learn how to upload your static site and have it served by GitLab by following the %{link_start}documentation on GitLab Pages%{link_end}.').html_safe % { link_start: link_start,
- link_end: link_end }
+ = s_('GitLabPages|See the %{docs_link_start}GitLab Pages documentation%{link_end} to learn how to upload your static site and have GitLab serve it. You can also follow a %{samples_link_start}sample project%{link_end} or use a %{templates_link_start}GitLab CI template%{link_end}.').html_safe % { docs_link_start: docs_link_start, samples_link_start: samples_link_start, templates_link_start: templates_link_start, link_end: link_end }
diff --git a/app/views/shared/access_tokens/_form.html.haml b/app/views/shared/access_tokens/_form.html.haml
index f206a2152c2..089643f4748 100644
--- a/app/views/shared/access_tokens/_form.html.haml
+++ b/app/views/shared/access_tokens/_form.html.haml
@@ -30,4 +30,4 @@
= render 'shared/tokens/scopes_form', prefix: prefix, token: token, scopes: scopes
.gl-mt-3
- = f.submit _('Create %{type}') % { type: type }, class: 'btn btn-success', data: { qa_selector: 'create_token_button' }
+ = f.submit _('Create %{type}') % { type: type }, class: 'gl-button btn btn-success', data: { qa_selector: 'create_token_button' }
diff --git a/app/views/shared/access_tokens/_table.html.haml b/app/views/shared/access_tokens/_table.html.haml
index 50daa400e6c..d7c74255578 100644
--- a/app/views/shared/access_tokens/_table.html.haml
+++ b/app/views/shared/access_tokens/_table.html.haml
@@ -43,7 +43,7 @@
- else
%span.token-never-expires-label= _('Never')
%td= token.scopes.present? ? token.scopes.join(', ') : _('no scopes selected')
- %td= link_to _('Revoke'), revoke_route_helper.call(token), method: :put, class: 'btn btn-danger float-right qa-revoke-button', data: { confirm: _('Are you sure you want to revoke this %{type}? This action cannot be undone.') % { type: type } }
+ %td= link_to _('Revoke'), revoke_route_helper.call(token), method: :put, class: 'gl-button btn btn-danger float-right qa-revoke-button', data: { confirm: _('Are you sure you want to revoke this %{type}? This action cannot be undone.') % { type: type } }
- else
.settings-message.text-center
= no_active_tokens_message