Commit 57202a27 authored by Douwe Maan's avatar Douwe Maan

Merge branch 'master' into 'jej/github-project-service-for-ci'

# Conflicts:
#   ee/app/models/license.rb
parents f575d1d9 eb091439
This diff is collapsed.
......@@ -197,6 +197,17 @@ release. There are two levels of priority labels:
milestone. If these issues are not done in the current release, they will
strongly be considered for the next release.
### Severity labels (~S1, ~S2, etc.)
Severity labels help us clearly communicate the impact of a ~bug on users.
| Label | Meaning | Example |
|-------|------------------------------------------|---------|
| ~S1 | Feature broken, no workaround | Unable to create an issue |
| ~S2 | Feature broken, workaround unacceptable | Can push commits, but only via the command line |
| ~S3 | Feature broken, workaround acceptable | Can create merge requests only from the Merge Requests page, not through the Issue |
| ~S4 | Cosmetic issue | Label colors are incorrect / not being displayed |
### Label for community contributors (~"Accepting Merge Requests")
Issues that are beneficial to our users, 'nice to haves', that we currently do
......
......@@ -426,7 +426,7 @@ group :ed25519 do
end
# Gitaly GRPC client
gem 'gitaly-proto', '~> 0.87.0', require: 'gitaly'
gem 'gitaly-proto', '~> 0.88.0', require: 'gitaly'
# Explicitly lock grpc as we know 1.9 is bad
# 1.10 is still being tested. See gitlab-org/gitaly#1059
gem 'grpc', '~> 1.8.3'
......
......@@ -309,7 +309,7 @@ GEM
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
gherkin-ruby (0.3.2)
gitaly-proto (0.87.0)
gitaly-proto (0.88.0)
google-protobuf (~> 3.1)
grpc (~> 1.0)
github-linguist (5.3.3)
......@@ -630,7 +630,7 @@ GEM
atomic (>= 1.0.0)
mysql2
peek
peek-performance_bar (1.3.0)
peek-performance_bar (1.3.1)
peek (>= 0.1.0)
peek-pg (1.3.0)
concurrent-ruby
......@@ -1091,7 +1091,7 @@ DEPENDENCIES
gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.2.0)
gitaly-proto (~> 0.87.0)
gitaly-proto (~> 0.88.0)
github-linguist (~> 5.3.3)
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-license (~> 1.0)
......
......@@ -7,6 +7,7 @@ export default class FilteredSearchBoards extends FilteredSearchManager {
constructor(store, updateUrl = false, cantEdit = []) {
super({
page: 'boards',
isGroup: true,
filteredSearchTokenKeys: FilteredSearchTokenKeysIssues,
stateFiltersSelector: '.issues-state-filters',
});
......
import Vue from 'vue';
import '../vue_shared/vue_resource_interceptor';
if (process.env.NODE_ENV !== 'production') {
Vue.config.productionTip = false;
......
......@@ -125,6 +125,16 @@ export default class FilteredSearchDropdownManager {
endpoint = `${endpoint}?only_group_labels=true`;
}
// EE-only
if (this.groupAncestor) {
endpoint = `${endpoint}&include_ancestor_groups=true`;
}
// EE-only
if (this.isGroupDecendent) {
endpoint = `${endpoint}&include_descendant_groups=true`;
}
return endpoint;
}
......
......@@ -109,6 +109,7 @@ export default class FilteredSearchManager {
page: this.page,
isGroup: this.isGroup,
isGroupAncestor: this.isGroupAncestor,
isGroupDecendent: this.isGroupDecendent,
filteredSearchTokenKeys: this.filteredSearchTokenKeys,
});
......
......@@ -152,14 +152,14 @@ export default {
showLeaveGroupModal(group, parentGroup) {
this.targetGroup = group;
this.targetParentGroup = parentGroup;
this.updateModal = true;
this.showModal = true;
this.groupLeaveConfirmationMessage = s__(`GroupsTree|Are you sure you want to leave the "${group.fullName}" group?`);
},
hideLeaveGroupModal() {
this.updateModal = false;
this.showModal = false;
},
leaveGroup() {
this.updateModal = false;
this.showModal = false;
this.targetGroup.isBeingRemoved = true;
this.service.leaveGroup(this.targetGroup.leavePath)
.then(res => res.json())
......@@ -208,9 +208,9 @@ export default {
:page-info="pageInfo"
/>
<modal
v-show="showModal"
:primary-button-label="__('Leave')"
v-if="showModal"
kind="warning"
:primary-button-label="__('Leave')"
:title="__('Are you sure?')"
:text="groupLeaveConfirmationMessage"
@cancel="hideLeaveGroupModal"
......
......@@ -8,4 +8,5 @@ export default {
OK: 200,
MULTIPLE_CHOICES: 300,
BAD_REQUEST: 400,
NOT_FOUND: 404,
};
......@@ -216,6 +216,9 @@ export default class MilestoneSelect {
$value.html(milestoneLinkNoneTemplate);
return $sidebarCollapsedValue.find('span').text('No');
}
})
.catch(() => {
$loading.fadeOut();
});
}
}
......
......@@ -88,7 +88,7 @@ export default {
</script>
<template>
<div class="block labels">
<div class="block labels js-labels-block">
<dropdown-value-collapsed
v-if="showCreate"
:labels="context.labels"
......@@ -104,7 +104,7 @@ export default {
</dropdown-value>
<div
v-if="canEdit"
class="selectbox"
class="selectbox js-selectbox"
style="display: none;"
>
<dropdown-hidden-input
......
......@@ -35,7 +35,7 @@ export default {
</script>
<template>
<div class="hide-collapsed value issuable-show-labels">
<div class="hide-collapsed value issuable-show-labels js-value">
<span
v-if="isEmpty"
class="text-secondary"
......
......@@ -9,7 +9,6 @@ class Admin::ImpersonationTokensController < Admin::ApplicationController
@impersonation_token = finder.build(impersonation_token_params)
if @impersonation_token.save
flash[:impersonation_token] = @impersonation_token.token
redirect_to admin_user_impersonation_tokens_path, notice: "A new impersonation token has been created."
else
set_index_vars
......
module Boards
class IssuesController < Boards::ApplicationController
prepend EE::BoardsResponses
prepend EE::Boards::IssuesController
include BoardsResponses
include ControllerWithCrossProjectAccessCheck
requires_cross_project_access if: -> { board&.group_board? }
before_action :whitelist_query_limiting, only: [:index, :update]
before_action :authorize_read_issue, only: [:index]
......@@ -66,11 +67,19 @@ module Boards
end
def issues_finder
IssuesFinder.new(current_user, project_id: board_parent.id)
if board.group_board?
IssuesFinder.new(current_user, group_id: board_parent.id)
else
IssuesFinder.new(current_user, project_id: board_parent.id)
end
end
def project
board_parent
@project ||= if board.group_board?
Project.find(issue_params[:project_id])
else
board_parent
end
end
def move_params
......
module Boards
class ListsController < Boards::ApplicationController
prepend EE::BoardsResponses
include BoardsResponses
before_action :authorize_admin_list, only: [:create, :update, :destroy, :generate]
......
module BoardsResponses
include Gitlab::Utils::StrongMemoize
def board_params
params.require(:board).permit(:name, :weight, :milestone_id, :assignee_id, label_ids: [])
end
def parent
strong_memoize(:parent) do
group? ? group : project
end
end
def boards_path
if group?
group_boards_path(parent)
else
project_boards_path(parent)
end
end
def board_path(board)
if group?
group_board_path(parent, board)
else
project_board_path(parent, board)
end
end
def group?
instance_variable_defined?(:@group)
end
def authorize_read_list
authorize_action_for!(board.parent, :read_list)
ability = board.group_board? ? :read_group : :read_list
authorize_action_for!(board.parent, ability)
end
def authorize_read_issue
authorize_action_for!(board.parent, :read_issue)
ability = board.group_board? ? :read_group : :read_issue
authorize_action_for!(board.parent, ability)
end
def authorize_update_issue
......@@ -31,6 +67,10 @@ module BoardsResponses
respond_with(@board) # rubocop:disable Gitlab/ModuleWithInstanceVariables
end
def serialize_as_json(resource)
resource.as_json(only: [:id])
end
def respond_with(resource)
respond_to do |format|
format.html
......
class Groups::BoardsController < Groups::ApplicationController
prepend EE::Boards::BoardsController
prepend EE::BoardsResponses
include BoardsResponses
before_action :check_group_issue_boards_available!
before_action :assign_endpoint_vars
def index
......@@ -23,4 +21,8 @@ class Groups::BoardsController < Groups::ApplicationController
@namespace_path = group.to_param
@labels_endpoint = group_labels_url(group)
end
def serialize_as_json(resource)
resource.as_json(only: [:id])
end
end
......@@ -62,7 +62,7 @@ class InvitesController < ApplicationController
case source
when Project
project = member.source
label = "project #{project.name_with_namespace}"
label = "project #{project.full_name}"
path = project_path(project)
when Group
group = member.source
......
......@@ -38,7 +38,7 @@ class Projects::BlobController < Projects::ApplicationController
end
format.json do
page_title @blob.path, @ref, @project.name_with_namespace
page_title @blob.path, @ref, @project.full_name
show_json
end
......
class Projects::BoardsController < Projects::ApplicationController
prepend EE::Boards::BoardsController
prepend EE::BoardsResponses
include BoardsResponses
include IssuableCollections
......
......@@ -7,13 +7,19 @@ class Projects::BranchesController < Projects::ApplicationController
before_action :authorize_download_code!
before_action :authorize_push_code!, only: [:new, :create, :destroy, :destroy_all_merged]
def index
@sort = params[:sort].presence || sort_value_recently_updated
@branches = BranchesFinder.new(@repository, params.merge(sort: @sort)).execute
@branches = Kaminari.paginate_array(@branches).page(params[:page])
# Support legacy URLs
before_action :redirect_for_legacy_index_sort_or_search, only: [:index]
def index
respond_to do |format|
format.html do
@sort = params[:sort].presence || sort_value_recently_updated
@mode = params[:state].presence || 'overview'
@overview_max_branches = 5
# Fetch branches for the specified mode
fetch_branches_by_mode
@refs_pipelines = @project.pipelines.latest_successful_for_refs(@branches.map(&:name))
@merged_branch_names =
repository.merged_branch_names(@branches.map(&:name))
......@@ -28,7 +34,9 @@ class Projects::BranchesController < Projects::ApplicationController
end
end
format.json do
render json: @branches.map(&:name)
branches = BranchesFinder.new(@repository, params).execute
branches = Kaminari.paginate_array(branches).page(params[:page])
render json: branches.map(&:name)
end
end
end
......@@ -123,4 +131,27 @@ class Projects::BranchesController < Projects::ApplicationController
context: 'autodeploy'
)
end
def redirect_for_legacy_index_sort_or_search
# Normalize a legacy URL with redirect
if request.format != :json && !params[:state].presence && [:sort, :search, :page].any? { |key| params[key].presence }
redirect_to project_branches_filtered_path(@project, state: 'all'), notice: 'Update your bookmarked URLs as filtered/sorted branches URL has been changed.'
end
end
def fetch_branches_by_mode
if @mode == 'overview'
# overview mode
@active_branches, @stale_branches = BranchesFinder.new(@repository, sort: sort_value_recently_updated).execute.partition(&:active?)
# Here we get one more branch to indicate if there are more data we're not showing
@active_branches = @active_branches.first(@overview_max_branches + 1)
@stale_branches = @stale_branches.first(@overview_max_branches + 1)
@branches = @active_branches + @stale_branches
else
# active/stale/all view mode
@branches = BranchesFinder.new(@repository, params.merge(sort: @sort)).execute
@branches = @branches.select { |b| b.state.to_s == @mode } if %w[active stale].include?(@mode)
@branches = Kaminari.paginate_array(@branches).page(params[:page])
end
end
end
......@@ -17,10 +17,8 @@ class Projects::CompareController < Projects::ApplicationController
def show
apply_diff_view_cookie!
# n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37430
Gitlab::GitalyClient.allow_n_plus_1_calls do
render
end
render
end
def diff_for_path
......
......@@ -24,7 +24,7 @@ class Projects::DeploymentsController < Projects::ApplicationController
end
def additional_metrics
return render_404 unless deployment.has_additional_metrics?
return render_404 unless deployment.has_metrics?
respond_to do |format|
format.json do
......
......@@ -9,25 +9,22 @@ class Projects::NetworkController < Projects::ApplicationController
before_action :assign_commit
def show
# n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37602
Gitlab::GitalyClient.allow_n_plus_1_calls do
@url = project_network_path(@project, @ref, @options.merge(format: :json))
@commit_url = project_commit_path(@project, 'ae45ca32').gsub("ae45ca32", "%s")
respond_to do |format|
format.html do
if @options[:extended_sha1] && !@commit
flash.now[:alert] = "Git revision '#{@options[:extended_sha1]}' does not exist."
end
end
@url = project_network_path(@project, @ref, @options.merge(format: :json))
@commit_url = project_commit_path(@project, 'ae45ca32').gsub("ae45ca32", "%s")
format.json do
@graph = Network::Graph.new(project, @ref, @commit, @options[:filter_ref])
respond_to do |format|
format.html do
if @options[:extended_sha1] && !@commit
flash.now[:alert] = "Git revision '#{@options[:extended_sha1]}' does not exist."
end
end
render
format.json do
@graph = Network::Graph.new(project, @ref, @commit, @options[:filter_ref])
end
end
render
end
def assign_commit
......
......@@ -2,11 +2,12 @@ module Projects
module Prometheus
class MetricsController < Projects::ApplicationController
before_action :authorize_admin_project!
before_action :require_prometheus_metrics!
def active_common
respond_to do |format|
format.json do
matched_metrics = prometheus_service.matched_metrics || {}
matched_metrics = prometheus_adapter.query(:matched_metrics) || {}
if matched_metrics.any?
render json: matched_metrics
......@@ -19,8 +20,12 @@ module Projects
private
def prometheus_service
@prometheus_service ||= project.find_or_initialize_service('prometheus')
def prometheus_adapter
@prometheus_adapter ||= ::Prometheus::AdapterService.new(project).prometheus_adapter
end
def require_prometheus_metrics!
render_404 unless prometheus_adapter.can_query?
end
end
end
......
......@@ -36,7 +36,7 @@ class Projects::TreeController < Projects::ApplicationController
end
format.json do
page_title @path.presence || _("Files"), @ref, @project.name_with_namespace
page_title @path.presence || _("Files"), @ref, @project.full_name
# n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/38261
Gitlab::GitalyClient.allow_n_plus_1_calls do
......
......@@ -132,7 +132,7 @@ class ProjectsController < Projects::ApplicationController
return access_denied! unless can?(current_user, :remove_project, @project)
::Projects::DestroyService.new(@project, current_user, {}).async_execute
flash[:notice] = _("Project '%{project_name}' is in the process of being deleted.") % { project_name: @project.name_with_namespace }
flash[:notice] = _("Project '%{project_name}' is in the process of being deleted.") % { project_name: @project.full_name }
redirect_to dashboard_projects_path, status: 302
rescue Projects::DestroyService::DestroyError => ex
......
class BranchesFinder
def initialize(repository, params)
def initialize(repository, params = {})
@repository = repository
@params = params
end
......
......@@ -19,6 +19,10 @@
# non_archived: boolean
# iids: integer[]
# my_reaction_emoji: string
# created_after: datetime
# created_before: datetime
# updated_after: datetime
# updated_before: datetime
#
class IssuableFinder
prepend FinderWithCrossProjectAccess
......@@ -79,6 +83,7 @@ class IssuableFinder
def filter_items(items)
items = by_scope(items)
items = by_created_at(items)
items = by_updated_at(items)
items = by_state(items)
items = by_group(items)
items = by_search(items)
......@@ -283,6 +288,13 @@ class IssuableFinder
end
end
def by_updated_at(items)
items = items.updated_after(params[:updated_after]) if params[:updated_after].present?
items = items.updated_before(params[:updated_before]) if params[:updated_before].present?
items
end
def by_state(items)
case params[:state].to_s
when 'closed'
......
......@@ -17,6 +17,10 @@
# my_reaction_emoji: string
# public_only: boolean
# due_date: date or '0', '', 'overdue', 'week', or 'month'
# created_after: datetime
# created_before: datetime
# updated_after: datetime
# updated_before: datetime
#
class IssuesFinder < IssuableFinder
prepend EE::IssuesFinder
......
......@@ -19,6 +19,10 @@
# my_reaction_emoji: string
# source_branch: string
# target_branch: string
# created_after: datetime
# created_before: datetime
# updated_after: datetime
# updated_before: datetime
#
class MergeRequestsFinder < IssuableFinder
def klass
......
......@@ -150,9 +150,7 @@ class TodosFinder
if project?
items.where(project: project)
else
projects = Project
.public_or_visible_to_user(current_user)
.order_id_desc
projects = Project.public_or_visible_to_user(current_user)
items.joins(:project).merge(projects)
end
......
......@@ -19,23 +19,35 @@ module BoardsHelper
end
def build_issue_link_base
project_issues_path(@project)
if board.group_board?
"#{group_path(@board.group)}/:project_path/issues"
else
project_issues_path(@project)
end
end
def board_base_url
project_boards_path(@project)
if board.group_board?
group_boards_url(@group)
else
project_boards_path(@project)
end
end
def multiple_boards_available?
current_board_parent.multiple_issue_boards_available?(current_user)
current_board_parent.multiple_issue_boards_available?
end
def current_board_path(board)
@current_board_path ||= project_board_path(current_board_parent, board)
@current_board_path ||= if board.group_board?
group_board_path(current_board_parent, board)
else
project_board_path(current_board_parent, board)
end
end
def current_board_parent
@current_board_parent ||= @project
@current_board_parent ||= @group || @project
end
def can_admin_issue?
......@@ -49,7 +61,8 @@ module BoardsHelper
labels: labels_filter_path(true),
labels_endpoint: @labels_endpoint,
namespace_path: @namespace_path,
project_path: @project&.try(:path)
project_path: @project&.path,
group_path: @group&.path
}
end
......@@ -61,7 +74,8 @@ module BoardsHelper
field_name: 'issue[assignee_ids][]',
first_user: current_user&.username,
current_user: 'true',
project_id: @project&.try(:id),
project_id: @project&.id,
group_id: @group&.id,
null_user: 'true',
multi_select: 'true',
'dropdown-header': dropdown_options[:data][:'dropdown-header'],
......
module BranchesHelper
prepend EE::BranchesHelper
def filter_branches_path(options = {})
exist_opts = {
search: params[:search],
sort: params[:sort]
}
options = exist_opts.merge(options)
project_branches_path(@project, @id, options)
end
def project_branches
options_for_select(@project.repository.branch_names, @project.default_branch)
end
......
......@@ -30,7 +30,7 @@ module FormHelper
null_user: true,
current_user: true,
project_id: @project&.id,
field_name: "issue[assignee_ids][]",
field_name: 'issue[assignee_ids][]',
default_label: 'Unassigned',
'max-select': 1,
'dropdown-header': 'Assignee',
......
......@@ -137,7 +137,7 @@ module GroupsHelper
links = [:overview, :group_members]
if can?(current_user, :read_cross_project)
links += [:activity, :issues, :labels, :milestones, :merge_requests]
links += [:activity, :issues, :boards, :labels, :milestones, :merge_requests]
end
if can?(current_user, :admin_group, @group)
......
......@@ -8,10 +8,10 @@ module ImportHelper
"#{namespace}/#{name}"
end
def provider_project_link(provider, path_with_namespace)
url = __send__("#{provider}_project_url", path_with_namespace) # rubocop:disable GitlabSecurity/PublicSend
def provider_project_link(provider, full_path)
url = __send__("#{provider}_project_url", full_path) # rubocop:disable GitlabSecurity/PublicSend
link_to path_with_namespace, url, target: '_blank', rel: 'noopener noreferrer'
link_to full_path, url, target: '_blank', rel: 'noopener noreferrer'
end
def import_will_timeout_message(_ci_cd_only)
......@@ -38,8 +38,8 @@ module ImportHelper
private
def github_project_url(path_with_namespace)
"#{github_root_url}/#{path_with_namespace}"
def github_project_url(full_path)
"#{github_root_url}/#{full_path}"
end
def github_root_url
......@@ -49,7 +49,7 @@ module ImportHelper
@github_url = provider.fetch('url', 'https://github.com') if provider
end
def gitea_project_url(path_with_namespace)
"#{@gitea_host_url.sub(%r{/+\z}, '')}/#{path_with_namespace}"
def gitea_project_url(full_path)
"#{@gitea_host_url.sub(%r{/+\z}, '')}/#{full_path}"
end
end
......@@ -99,7 +99,7 @@ module IssuablesHelper
project = Project.find_by(id: project_id)
if project
project.name_with_namespace
project.full_name
else
default_label
end
......
......@@ -99,13 +99,13 @@ module ProjectsHelper
end
def remove_project_message(project)
_("You are going to remove %{project_name_with_namespace}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?") %
{ project_name_with_namespace: project.name_with_namespace }
_("You are going to remove %{project_full_name}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?") %
{ project_full_name: project.full_name }
end
def transfer_project_message(project)
_("You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?") %
{ project_name_with_namespace: project.name_with_namespace }
_("You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?") %
{ project_full_name: project.full_name }
end
def remove_fork_project_message(project)
......
......@@ -120,7 +120,7 @@ module SearchHelper
category: "Projects",
id: p.id,
value: "#{search_result_sanitize(p.name)}",
label: "#{search_result_sanitize(p.name_with_namespace)}",
label: "#{search_result_sanitize(p.full_name)}",
url: project_path(p)
}
end
......
......@@ -114,7 +114,7 @@ module TodosHelper
projects = current_user.authorized_projects.sorted_by_activity.non_archived.with_route
projects = projects.map do |project|
{ id: project.id, text: project.name_with_namespace }
{ id: project.id, text: project.full_name }
end
projects.unshift({ id: '', text: 'Any Project' }).to_json
......
......@@ -38,5 +38,13 @@ module Emails
reply_to: @message.reply_to,
subject: @message.subject)
end
def mirror_was_hard_failed_email(project_id, user_id)
@project = Project.find(project_id)
user = User.find(user_id)
mail(to: user.notification_email,
subject: subject('Repository mirroring paused'))
end
end
end
class Board < ActiveRecord::Base
prepend EE::Board
belongs_to :group
belongs_to :project
has_many :lists, -> { order(:list_type, :position) }, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
validates :project, presence: true, if: :project_needed?
validates :group, presence: true, unless: :project
def project_needed?
true
!group
end
def parent
project
@parent ||= group || project
end
def group_board?
false
group_id.present?
end
def backlog_list
......
module Clusters
module Applications
class Prometheus < ActiveRecord::Base
include PrometheusAdapter
VERSION = "2.0.0".freeze
self.table_name = 'clusters_applications_prometheus'
......@@ -39,7 +41,7 @@ module Clusters
)
end
def proxy_client
def prometheus_client
return unless kube_client
proxy_url = kube_client.proxy_url('service', service_name, service_port, Gitlab::Kubernetes::Helm::NAMESPACE)
......
......@@ -56,12 +56,13 @@ module Clusters
def specification
{
"gitlabUrl" => gitlab_url,
"runnerToken" => ensure_runner.token
"runnerToken" => ensure_runner.token,
"runners" => { "privileged" => privileged }
}
end
def content_values
specification.merge(YAML.load_file(chart_values_file))
YAML.load_file(chart_values_file).deep_merge!(specification)
end
end
end
......
......@@ -53,9 +53,6 @@ module Clusters
scope :enabled, -> { where(enabled: true) }
scope :disabled, -> { where(enabled: false) }
scope :for_environment, -> (env) { where(environment_scope: ['*', '', env.slug]) }
scope :for_all_environments, -> { where(environment_scope: ['*', '']) }
def status_name
if provider
provider.status_name
......
module DeploymentPlatform
# EE would override this and utilize the extra argument
def deployment_platform(environment: nil)
@deployment_platform ||=
find_cluster_platform_kubernetes ||
......
......@@ -19,6 +19,7 @@ module Issuable
include AfterCommitQueue
include Sortable
include CreatedAtFilterable
include UpdatedAtFilterable
# This object is used to gather issuable meta data for displaying
# upvotes, downvotes, notes and closing merge requests count for issues and merge requests
......@@ -232,6 +233,10 @@ module Issuable
def to_ability_name
model_name.singular
end
def parent_class
::Project
end
end
def today?
......
module PrometheusAdapter
extend ActiveSupport::Concern
included do
include ReactiveCaching
self.reactive_cache_key = ->(adapter) { [adapter.class.model_name.singular, adapter.id] }
self.reactive_cache_lease_timeout = 30.seconds
self.reactive_cache_refresh_interval = 30.seconds
self.reactive_cache_lifetime = 1.minute
def prometheus_client
raise NotImplementedError
end
def prometheus_client_wrapper
Gitlab::PrometheusClient.new(prometheus_client)
end
def can_query?
prometheus_client.present?
end
def query(query_name, *args)
return unless can_query?
query_class = Gitlab::Prometheus::Queries.const_get("#{query_name.to_s.classify}Query")
args.map!(&:id)
with_reactive_cache(query_class.name, *args, &query_class.method(:transform_reactive_result))
end
# Cache metrics for specific environment
def calculate_reactive_cache(query_class_name, *args)
return unless prometheus_client
data = Kernel.const_get(query_class_name).new(prometheus_client_wrapper).query(*args)
{
success: true,
data: data,
last_update: Time.now.utc
}
rescue Gitlab::PrometheusClient::Error => err
{ success: false, result: err.message }
end
end
end
module UpdatedAtFilterable
extend ActiveSupport::Concern
included do
scope :updated_before, ->(date) { where(scoped_table[:updated_at].lteq(date)) }
scope :updated_after, ->(date) { where(scoped_table[:updated_at].gteq(date)) }
def self.scoped_table
arel_table.alias(table_name)
end
end
end
......@@ -98,28 +98,29 @@ class Deployment < ActiveRecord::Base
end
def has_metrics?
project.monitoring_service.present?
prometheus_adapter&.can_query?
end
def metrics
return {} unless has_metrics?
project.monitoring_service.deployment_metrics(self)
end
def has_additional_metrics?
project.prometheus_service.present?
metrics = prometheus_adapter.query(:deployment, self)
metrics&.merge(deployment_time: created_at.to_i) || {}
end
def additional_metrics
return {} unless project.prometheus_service.present?
return {} unless has_metrics?
metrics = project.prometheus_service.additional_deployment_metrics(self)
metrics = prometheus_adapter.query(:additional_metrics_deployment, self)
metrics&.merge(deployment_time: created_at.to_i) || {}
end
private
def prometheus_adapter
environment.prometheus_adapter
end
def ref_path
File.join(environment.ref_path, 'deployments', iid.to_s)
end
......
......@@ -3,7 +3,7 @@
# A note of this type can be resolvable.
class DiscussionNote < Note
# Names of all implementers of `Noteable` that support discussions.
NOTEABLE_TYPES = %w(MergeRequest Issue Commit Snippet).freeze
NOTEABLE_TYPES = %w(MergeRequest Issue Commit Snippet Epic).freeze
validates :noteable_type, inclusion: { in: NOTEABLE_TYPES }
......
......@@ -137,10 +137,6 @@ class Environment < ActiveRecord::Base
end
end
def deployment_platform
project.deployment_platform(environment: self)
end
def has_terminals?
deployment_platform.present? && available? && last_deployment.present?
end
......@@ -154,21 +150,19 @@ class Environment < ActiveRecord::Base
end
def has_metrics?
project.monitoring_service.present? && available? && last_deployment.present?
prometheus_adapter&.can_query? && available? && last_deployment.present?
end
def metrics
project.monitoring_service.environment_metrics(self) if has_metrics?
prometheus_adapter.query(:environment, self) if has_metrics?
end
def has_additional_metrics?
project.prometheus_service.present? && available? && last_deployment.present?
def additional_metrics
prometheus_adapter.query(:additional_metrics_environment, self) if has_metrics?
end
def additional_metrics
if has_additional_metrics?
project.prometheus_service.additional_environment_metrics(self)
end
def prometheus_adapter
@prometheus_adapter ||= Prometheus::AdapterService.new(project, deployment_platform).prometheus_adapter
end
def slug
......@@ -234,6 +228,10 @@ class Environment < ActiveRecord::Base
self.environment_type || self.name
end
def deployment_platform
project.deployment_platform(environment: self)
end
private
# Slugifying a name may remove the uniqueness guarantee afforded by it being
......
......@@ -164,7 +164,7 @@ class Event < ActiveRecord::Base
def project_name
if project
project.name_with_namespace
project.full_name
else
"(deleted project)"
end
......
......@@ -36,6 +36,8 @@ class Group < Namespace
has_many :hooks, dependent: :destroy, class_name: 'GroupHook' # rubocop:disable Cop/ActiveRecordDependent
has_many :uploads, as: :model, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :boards
# We cannot simply set `has_many :audit_events, as: :entity, dependent: :destroy`
# here since Group inherits from Namespace, the entity_type would be set to `Namespace`.
has_many :audit_events, -> { where(entity_type: Group) }, foreign_key: 'entity_id'
......
class Label < ActiveRecord::Base
# EE specific
prepend EE::Label
include CacheMarkdownField
include Referable
include Subscribable
......@@ -38,6 +35,7 @@ class Label < ActiveRecord::Base
scope :templates, -> { where(template: true) }
scope :with_title, ->(title) { where(title: title) }
scope :with_lists_and_board, -> { joins(lists: :board).merge(List.movable) }
scope :on_group_boards, ->(group_id) { with_lists_and_board.where(boards: { group_id: group_id }) }
scope :on_project_boards, ->(project_id) { with_lists_and_board.where(boards: { project_id: project_id }) }
def self.prioritized(project)
......
......@@ -197,10 +197,6 @@ class MergeRequestDiff < ActiveRecord::Base
CompareService.new(project, head_commit_sha).execute(project, sha, straight: true)
end
def commits_count
super || merge_request_diff_commits.size
end
private
def create_merge_request_diff_files(diffs)
......
......@@ -232,9 +232,9 @@ class Namespace < ActiveRecord::Base
has_parent?
end
## EE only
def multiple_issue_boards_available?(user = nil)
feature_available?(:multiple_issue_boards)
# Overridden on EE module
def multiple_issue_boards_available?
false
end
def full_path_was
......
......@@ -24,12 +24,7 @@ module Network
end
def parents(map)
@commit.parents.map do |p|
if map.include?(p.id)
map[p.id]
end
end
.compact
map.values_at(*@commit.parent_ids).compact
end
end
end
......@@ -84,7 +84,7 @@ class Note < ActiveRecord::Base
validates :author, presence: true
validates :discussion_id, presence: true, format: { with: /\A\h{40}\z/ }
validate unless: [:for_commit?, :importing?, :for_personal_snippet?] do |note|
validate unless: [:for_commit?, :importing?, :skip_project_check?] do |note|
unless note.noteable.try(:project) == note.project
errors.add(:project, 'does not match noteable project')
end
......@@ -236,7 +236,7 @@ class Note < ActiveRecord::Base
end
def skip_project_check?
for_personal_snippet?
!for_project_noteable?
end
def commit
......@@ -316,6 +316,11 @@ class Note < ActiveRecord::Base
self.noteable.supports_discussions? && !part_of_discussion?
end
def can_create_todo?
# Skip system notes, and notes on project snippet
!system? && !for_snippet?
end
def discussion_class(noteable = nil)
# When commit notes are rendered on an MR's Discussion page, they are
# displayed in one discussion instead of individually.
......
......@@ -1693,8 +1693,9 @@ class Project < ActiveRecord::Base
end
end
def multiple_issue_boards_available?(user)
feature_available?(:multiple_issue_boards, user)
# Overridden on EE module
def multiple_issue_boards_available?
false
end
def full_path_was
......
......@@ -68,7 +68,7 @@ http://app.asana.com/-/account_api'
end
user = data[:user_name]
project_name = project.name_with_namespace
project_name = project.full_name
data[:commits].each do |commit|
push_msg = "#{user} pushed to branch #{branch} of #{project_name} ( #{commit[:url]} ):"
......
......@@ -86,7 +86,7 @@ class CampfireService < Service
after = push[:after]
message = ""
message << "[#{project.name_with_namespace}] "
message << "[#{project.full_name}] "
message << "#{push[:user_name]} "
if Gitlab::Git.blank_ref?(before)
......
......@@ -129,7 +129,7 @@ class ChatNotificationService < Service
end
def project_name
project.name_with_namespace.gsub(/\s/, '')
project.full_name.gsub(/\s/, '')
end
def project_url
......
......@@ -120,7 +120,7 @@ class HipchatService < Service
else
message << "pushed to #{ref_type} <a href=\""\
"#{project.web_url}/commits/#{CGI.escape(ref)}\">#{ref}</a> "
message << "of <a href=\"#{project.web_url}\">#{project.name_with_namespace.gsub!(/\s/, '')}</a> "
message << "of <a href=\"#{project.web_url}\">#{project.full_name.gsub!(/\s/, '')}</a> "
message << "(<a href=\"#{project.web_url}/compare/#{before}...#{after}\">Compare changes</a>)"
push[:commits].take(MAX_COMMITS).each do |commit|
......@@ -275,7 +275,7 @@ class HipchatService < Service
end
def project_name
project.name_with_namespace.gsub(/\s/, '')
project.full_name.gsub(/\s/, '')
end
def project_url
......
class JiraService < IssueTrackerService
include Gitlab::Routing
include ApplicationHelper
include ActionView::Helpers::AssetUrlHelper
validates :url, url: true, presence: true, if: :activated?
validates :api_url, url: true, allow_blank: true
......@@ -268,7 +270,9 @@ class JiraService < IssueTrackerService
url: url,
title: title,
status: status,
icon: { title: 'GitLab', url16x16: 'https://gitlab.com/favicon.ico' }
icon: {
title: 'GitLab', url16x16: asset_url('favicon.ico', host: gitlab_config.url)
}
}
}
end
......
......@@ -37,7 +37,7 @@ class MattermostSlashCommandsService < SlashCommandsService
private
def command(params)
pretty_project_name = project.name_with_namespace
pretty_project_name = project.full_name
params.merge(
auto_complete: true,
......
......@@ -9,11 +9,11 @@ class MonitoringService < Service
%w()
end
def environment_metrics(environment)
def can_query?
raise NotImplementedError
end
def deployment_metrics(deployment)
def query(_, *_)
raise NotImplementedError
end
end
class PrometheusService < MonitoringService
include ReactiveService
self.reactive_cache_lease_timeout = 30.seconds
self.reactive_cache_refresh_interval = 30.seconds
self.reactive_cache_lifetime = 1.minute
include PrometheusAdapter
# Access to prometheus is directly through the API
prop_accessor :api_url
......@@ -13,7 +9,7 @@ class PrometheusService < MonitoringService
validates :api_url, url: true
end
before_save :synchronize_service_state!
before_save :synchronize_service_state
after_save :clear_reactive_cache!
......@@ -66,63 +62,15 @@ class PrometheusService < MonitoringService
# Check we can connect to the Prometheus API
def test(*args)
client.ping
Gitlab::PrometheusClient.new(prometheus_client).ping
{ success: true, result: 'Checked API endpoint' }
rescue Gitlab::PrometheusClient::Error => err
{ success: false, result: err }
end
def environment_metrics(environment)
with_reactive_cache(Gitlab::Prometheus::Queries::EnvironmentQuery.name, environment.id, &rename_field(:data, :metrics))
end
def deployment_metrics(deployment)
metrics = with_reactive_cache(Gitlab::Prometheus::Queries::DeploymentQuery.name, deployment.environment.id, deployment.id, &rename_field(:data, :metrics))
metrics&.merge(deployment_time: deployment.created_at.to_i) || {}
end
def additional_environment_metrics(environment)
with_reactive_cache(Gitlab::Prometheus::Queries::AdditionalMetricsEnvironmentQuery.name, environment.id, &:itself)
end
def additional_deployment_metrics(deployment)
with_reactive_cache(Gitlab::Prometheus::Queries::AdditionalMetricsDeploymentQuery.name, deployment.environment.id, deployment.id, &:itself)
end
def matched_metrics
with_reactive_cache(Gitlab::Prometheus::Queries::MatchedMetricsQuery.name, &:itself)
end
# Cache metrics for specific environment
def calculate_reactive_cache(query_class_name, *args)
return unless active? && project && !project.pending_delete?
environment_id = args.first
client = client(environment_id)
data = Kernel.const_get(query_class_name).new(client).query(*args)
{
success: true,
data: data,
last_update: Time.now.utc
}
rescue Gitlab::PrometheusClient::Error => err
{ success: false, result: err.message }
end
def client(environment_id = nil)
if manual_configuration?
Gitlab::PrometheusClient.new(RestClient::Resource.new(api_url))
else
cluster = cluster_with_prometheus(environment_id)
raise Gitlab::PrometheusClient::Error, "couldn't find cluster with Prometheus installed" unless cluster
rest_client = client_from_cluster(cluster)
raise Gitlab::PrometheusClient::Error, "couldn't create proxy Prometheus client" unless rest_client
Gitlab::PrometheusClient.new(rest_client)
end
def prometheus_client
RestClient::Resource.new(api_url) if api_url && manual_configuration? && active?
end
def prometheus_installed?
......@@ -134,32 +82,7 @@ class PrometheusService < MonitoringService
private
def cluster_with_prometheus(environment_id = nil)
clusters = if environment_id
::Environment.find_by(id: environment_id).try do |env|
# sort results by descending order based on environment_scope being longer
# thus more closely matching environment slug
project.clusters.enabled.for_environment(env).sort_by { |c| c.environment_scope&.length }.reverse!
end
else
project.clusters.enabled.for_all_environments
end
clusters&.detect { |cluster| cluster.application_prometheus&.installed? }
end
def client_from_cluster(cluster)
cluster.application_prometheus.proxy_client
end
def rename_field(old_field, new_field)
-> (metrics) do
metrics[new_field] = metrics.delete(old_field)
metrics
end
end
def synchronize_service_state!
def synchronize_service_state
self.active = prometheus_installed? || manual_configuration?
true
......
......@@ -88,10 +88,10 @@ class PushoverService < Service
user: user_key,
device: device,
priority: priority,
title: "#{project.name_with_namespace}",
title: "#{project.full_name}",
message: message,
url: data[:project][:web_url],
url_title: "See project #{project.name_with_namespace}"
url_title: "See project #{project.full_name}"
}
# Sound parameter MUST NOT be sent to API if not selected
......
......@@ -260,7 +260,7 @@ class Repository
# branches or tags, but we want to keep some of these commits around, for
# example if they have comments or CI builds.
def keep_around(sha)
return unless sha && commit_by(oid: sha)
return unless sha.present? && commit_by(oid: sha)
return if kept_around?(sha)
......@@ -915,20 +915,20 @@ class Repository
raw_repository.ancestor?(ancestor_id, descendant_id)
end
def fetch_as_mirror(url, forced: false, refmap: :all_refs, remote_name: nil)
def fetch_as_mirror(url, forced: false, refmap: :all_refs, remote_name: nil, prune: true)
unless remote_name
remote_name = "tmp-#{SecureRandom.hex}"
tmp_remote_name = true
end
add_remote(remote_name, url, mirror_refmap: refmap)
fetch_remote(remote_name, forced: forced)
fetch_remote(remote_name, forced: forced, prune: prune)
ensure
async_remove_remote(remote_name) if tmp_remote_name
end
def fetch_remote(remote, forced: false, ssh_auth: nil, no_tags: false)
gitlab_shell.fetch_remote(raw_repository, remote, ssh_auth: ssh_auth, forced: forced, no_tags: no_tags)
def fetch_remote(remote, forced: false, ssh_auth: nil, no_tags: false, prune: true)
gitlab_shell.fetch_remote(raw_repository, remote, ssh_auth: ssh_auth, forced: forced, no_tags: no_tags, prune: prune)
end
def async_remove_remote(remote_name)
......
......@@ -169,5 +169,9 @@ class Snippet < ActiveRecord::Base
def search_code(query)
fuzzy_search(query, [:content])
end
def parent_class
::Project
end
end
end
......@@ -32,8 +32,6 @@ class Todo < ActiveRecord::Base
validates :target_id, presence: true, unless: :for_commit?
validates :commit_id, presence: true, if: :for_commit?
default_scope { reorder(id: :desc) }
scope :pending, -> { with_state(:pending) }
scope :done, -> { with_state(:done) }
......@@ -53,10 +51,14 @@ class Todo < ActiveRecord::Base
# milestones, but still show something if the user has a URL with that
# selected.
def sort(method)
case method.to_s
when 'priority', 'label_priority' then order_by_labels_priority
else order_by(method)
end
sorted =
case method.to_s
when 'priority', 'label_priority' then order_by_labels_priority
else order_by(method)
end
# Break ties with the ID column for pagination
sorted.order(id: :desc)
end
# Order by priority depending on which issue/merge request the Todo belongs to
......
......@@ -51,7 +51,12 @@ class GroupPolicy < BasePolicy
rule { has_access }.enable :read_namespace
rule { developer }.enable :admin_milestones
rule { reporter }.enable :admin_label
rule { reporter }.policy do
enable :admin_label
enable :admin_list
enable :admin_issue
end
rule { master }.policy do
enable :create_projects
......
......@@ -41,7 +41,11 @@ module Boards
end
def set_parent
params[:project_id] = parent.id
if parent.is_a?(Group)
params[:group_id] = parent.id
else
params[:project_id] = parent.id
end
end
def set_state
......
module Boards
module Issues
class MoveService < Boards::BaseService
prepend EE::Boards::Issues::MoveService
def execute(issue)
return false unless can?(current_user, :update_issue, issue)
return false if issue_params.empty?
......@@ -62,8 +60,10 @@ module Boards
label_ids =
if moving_to_list.movable?
moving_from_list.label_id
elsif board.group_board?
::Label.on_group_boards(parent.id).pluck(:label_id)
else
Label.on_project_boards(parent.id).pluck(:label_id)
::Label.on_project_boards(parent.id).pluck(:label_id)
end
Array(label_ids).compact
......
module Boards
module Lists
class CreateService < Boards::BaseService
prepend EE::Boards::Lists::CreateService
def execute(board)
List.transaction do
label = available_labels_for(board).find(params[:label_id])
......@@ -14,7 +12,11 @@ module Boards
private
def available_labels_for(board)
LabelsFinder.new(current_user, project_id: parent.id).execute
if board.group_board?
parent.labels
else
LabelsFinder.new(current_user, project_id: parent.id).execute
end
end
def next_position(board)
......
module Ci
class CreateTraceArtifactService < BaseService
def execute(job)
return if job.job_artifacts_trace
job.trace.read do |stream|
break unless stream.file?
clone_file!(stream.path, JobArtifactUploader.workhorse_upload_path) do |clone_path|
create_job_trace!(job, clone_path)
FileUtils.rm(stream.path)
end
end
end
private
def create_job_trace!(job, path)
File.open(path) do |stream|
job.create_job_artifacts_trace!(
project: job.project,
file_type: :trace,
file: stream)
end
end
def clone_file!(src_path, temp_dir)
FileUtils.mkdir_p(temp_dir)
Dir.mktmpdir('tmp-trace', temp_dir) do |dir_path|
temp_path = File.join(dir_path, "job.log")
FileUtils.copy(src_path, temp_path)
yield(temp_path)
end
end
end
end
module MergeRequests
class UpdateService < MergeRequests::BaseService
prepend ::EE::MergeRequests::UpdateService
def execute(merge_request)
# We don't allow change of source/target projects and source branch
# after merge request was created
......
module Notes
class BuildService < ::BaseService
prepend ::EE::Notes::BuildService
def execute
in_reply_to_discussion_id = params.delete(:in_reply_to_discussion_id)
......@@ -26,14 +28,19 @@ module Notes
if project
project.notes.find_discussion(discussion_id)
else
# only PersonalSnippets can have discussions without project association
discussion = Note.find_discussion(discussion_id)
noteable = discussion.noteable
return nil unless noteable.is_a?(PersonalSnippet) && can?(current_user, :comment_personal_snippet, noteable)
return nil unless noteable_without_project?(noteable)
discussion
end
end
def noteable_without_project?(noteable)
return true if noteable.is_a?(PersonalSnippet) && can?(current_user, :comment_personal_snippet, noteable)
false
end
end
end
......@@ -11,7 +11,7 @@ module Notes
unless @note.system?
EventCreateService.new.leave_note(@note, @note.author)
return if @note.for_personal_snippet?
return unless @note.for_project_noteable?
@note.create_cross_references!
execute_note_hooks
......
......@@ -280,7 +280,7 @@ module NotificationRecipientService
add_participants(note.author)
add_mentions(note.author, target: note)
unless note.for_personal_snippet?
if note.for_project_noteable?
# Merge project watchers
add_project_watchers
......
module Projects
class UpdatePagesService < BaseService
InvaildStateError = Class.new(StandardError)
FailedToExtractError = Class.new(StandardError)
BLOCK_SIZE = 32.kilobytes
MAX_SIZE = 1.terabyte
SITE_PATH = 'public/'.freeze
......@@ -11,13 +14,15 @@ module Projects
end
def execute
register_attempt
# Create status notifying the deployment of pages
@status = create_status
@status.enqueue!
@status.run!
raise 'missing pages artifacts' unless build.artifacts?
raise 'pages are outdated' unless latest?
raise InvaildStateError, 'missing pages artifacts' unless build.artifacts?
raise InvaildStateError, 'pages are outdated' unless latest?
# Create temporary directory in which we will extract the artifacts
FileUtils.mkdir_p(tmp_path)
......@@ -26,24 +31,22 @@ module Projects
# Check if we did extract public directory
archive_public_path = File.join(archive_path, 'public')
raise 'pages miss the public folder' unless Dir.exist?(archive_public_path)
raise 'pages are outdated' unless latest?
raise FailedToExtractError, 'pages miss the public folder' unless Dir.exist?(archive_public_path)
raise InvaildStateError, 'pages are outdated' unless latest?
deploy_page!(archive_public_path)
success
end
rescue => e
rescue InvaildStateError, FailedToExtractError => e
register_failure
error(e.message)
ensure
register_attempt
build.erase_artifacts! unless build.has_expiring_artifacts?
end
private
def success
@status.success
delete_artifact!
super
end
......@@ -52,6 +55,7 @@ module Projects
@status.allow_failure = !latest?
@status.description = message
@status.drop(:script_failure)
delete_artifact!
super
end
......@@ -72,7 +76,7 @@ module Projects
elsif artifacts_filename.ends_with?('.zip')
extract_zip_archive!(temp_path)
else
raise 'unsupported artifacts format'
raise FailedToExtractError, 'unsupported artifacts format'
end
end
......@@ -82,18 +86,18 @@ module Projects
%W(dd bs=#{BLOCK_SIZE} count=#{blocks}),
%W(tar -x -C #{temp_path} #{SITE_PATH}),
err: '/dev/null')
raise 'pages failed to extract' unless results.compact.all?(&:success?)
raise FailedToExtractError, 'pages failed to extract' unless results.compact.all?(&:success?)
end
end
def extract_zip_archive!(temp_path)
raise 'missing artifacts metadata' unless build.artifacts_metadata?
raise FailedToExtractError, 'missing artifacts metadata' unless build.artifacts_metadata?
# Calculate page size after extract
public_entry = build.artifacts_metadata_entry(SITE_PATH, recursive: true)
if public_entry.total_size > max_size
raise "artifacts for pages are too large: #{public_entry.total_size}"
raise FailedToExtractError, "artifacts for pages are too large: #{public_entry.total_size}"
end
# Requires UnZip at least 6.00 Info-ZIP.
......@@ -103,7 +107,7 @@ module Projects
site_path = File.join(SITE_PATH, '*')
build.artifacts_file.use_file do |artifacts_path|
unless system(*%W(unzip -qq -n #{artifacts_path} #{site_path} -d #{temp_path}))
raise 'pages failed to extract'
raise FailedToExtractError, 'pages failed to extract'
end
end
end
......@@ -167,6 +171,11 @@ module Projects
build.ref
end
def delete_artifact!
build.reload # Reload stable object to prevent erase artifacts with old state
build.erase_artifacts! unless build.has_expiring_artifacts?
end
def latest_sha
project.commit(build.ref).try(:sha).to_s
end
......
module Prometheus
class AdapterService
def initialize(project, deployment_platform = nil)
@project = project
@deployment_platform = if deployment_platform
deployment_platform
else
project.deployment_platform
end
end
attr_reader :deployment_platform, :project
def prometheus_adapter
@prometheus_adapter ||= if service_prometheus_adapter.can_query?
service_prometheus_adapter
else
cluster_prometheus_adapter
end
end
def service_prometheus_adapter
project.find_or_initialize_service('prometheus')
end
def cluster_prometheus_adapter
return unless deployment_platform.respond_to?(:cluster)
cluster = deployment_platform.cluster
return unless cluster.application_prometheus&.installed?
cluster.application_prometheus
end
end
end
......@@ -22,8 +22,8 @@ class SystemHooksService
def build_event_data(model, event)
data = {
event_name: build_event_name(model, event),
created_at: model.created_at.xmlschema,
updated_at: model.updated_at.xmlschema
created_at: model.created_at&.xmlschema,
updated_at: model.updated_at&.xmlschema
}
case model
......
......@@ -254,8 +254,7 @@ class TodoService
end
def handle_note(note, author, skip_users = [])
# Skip system notes, and notes on project snippet
return if note.system? || note.for_snippet?
return unless note.can_create_todo?
project = note.project
target = note.noteable
......
......@@ -190,7 +190,7 @@
%h4 Latest projects
- @projects.each do |project|
%p
= link_to project.name_with_namespace, [:admin, project.namespace.becomes(Namespace), project], class: 'str-truncated-60'
= link_to project.full_name, [:admin, project.namespace.becomes(Namespace), project], class: 'str-truncated-60'
%span.light.pull-right
#{time_ago_with_tooltip(project.created_at)}
.col-md-4
......
......@@ -82,7 +82,7 @@
- @projects.each do |project|
%li
%strong
= link_to project.name_with_namespace, [:admin, project.namespace.becomes(Namespace), project]
= link_to project.full_name, [:admin, project.namespace.becomes(Namespace), project]
%span.badge
= storage_counter(project.statistics.storage_size)
%span.pull-right.light
......@@ -100,7 +100,7 @@
- @group.shared_projects.sort_by(&:name).each do |project|
%li
%strong
= link_to project.name_with_namespace, [:admin, project.namespace.becomes(Namespace), project]
= link_to project.full_name, [:admin, project.namespace.becomes(Namespace), project]
%span.badge
= storage_counter(project.statistics.storage_size)
%span.pull-right.light
......
- add_to_breadcrumbs "Projects", admin_projects_path
- breadcrumb_title @project.name_with_namespace
- page_title @project.name_with_namespace, "Projects"
- breadcrumb_title @project.full_name
- page_title @project.full_name, "Projects"
%h3.page-title
Project: #{@project.name_with_namespace}
Project: #{@project.full_name}
= link_to edit_project_path(@project), class: "btn btn-nr pull-right" do
%i.fa.fa-pencil-square-o
Edit
......
......@@ -39,7 +39,7 @@
%tr.alert-info
%td
%strong
= project.name_with_namespace
= project.full_name
%td
.pull-right
= link_to 'Disable', [:admin, project.namespace.becomes(Namespace), project, runner_project], method: :delete, class: 'btn btn-danger btn-xs'
......@@ -61,7 +61,7 @@
- @projects.each do |project|
%tr
%td
= project.name_with_namespace
= project.full_name
%td
.pull-right
= form_for [:admin, project.namespace.becomes(Namespace), project, project.runner_projects.new] do |f|
......@@ -95,7 +95,7 @@
%td.status
- if project
= project.name_with_namespace
= project.full_name
%td.build-link
- if project
......
......@@ -29,12 +29,12 @@
.panel.panel-default
.panel-heading Joined projects (#{@joined_projects.count})
%ul.well-list
- @joined_projects.sort_by(&:name_with_namespace).each do |project|
- @joined_projects.sort_by(&:full_name).each do |project|
- member = project.team.find_member(@user.id)
%li.project_member
.list-item-name
= link_to admin_project_path(project), class: dom_class(project) do
= project.name_with_namespace
= project.full_name
- if member
.pull-right
......
......@@ -2,9 +2,6 @@
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, params.merge(rss_url_options), title: "#{@group.name} issues")
- content_for :page_specific_javascripts do
= webpack_bundle_tag 'common_vue'
- if group_issues_count(state: 'all').zero?
= render 'shared/empty_states/issues', project_select_button: true
- else
......
- page_title 'Labels'
- issuables = ['issues', 'merge requests'] + (@group&.feature_available?(:epics) ? ['epics'] : [])
.top-area.adjust
.nav-text
Labels can be applied to issues and merge requests. Group labels are available for any project within the group.
= _("Labels can be applied to %{features}. Group labels are available for any project within the group.") % { features: issuables.to_sentence }
.nav-controls
- if can?(current_user, :admin_label, @group)
......@@ -16,4 +18,4 @@
= paginate @labels, theme: 'gitlab'
- else
.nothing-here-block
No labels created yet.
= _("No labels created yet.")
......@@ -14,7 +14,7 @@
.list-item-name
%span{ class: visibility_level_color(project.visibility_level) }
= visibility_level_icon(project.visibility_level)
%strong= link_to project.name_with_namespace, project
%strong= link_to project.full_name, project
.pull-right
- if project.archived
%span.label.label-warning archived
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment