Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
6046a605fd
commit
e829ca213b
|
@ -164,7 +164,7 @@ export default {
|
|||
<template>
|
||||
<form
|
||||
:class="{ 'was-validated': wasValidated }"
|
||||
class="prepend-top-default append-bottom-default needs-validation"
|
||||
class="prepend-top-default gl-mb-3 needs-validation"
|
||||
novalidate
|
||||
@submit.prevent.stop="onSubmit"
|
||||
>
|
||||
|
|
|
@ -96,7 +96,7 @@ export default {
|
|||
<preview-item :draft="draft" :is-last="isLast(index)" />
|
||||
</li>
|
||||
</ul>
|
||||
<gl-loading-icon v-else size="lg" class="prepend-top-default append-bottom-default" />
|
||||
<gl-loading-icon v-else size="lg" class="prepend-top-default gl-mb-3" />
|
||||
</div>
|
||||
<div class="dropdown-footer">
|
||||
<publish-button
|
||||
|
|
|
@ -62,9 +62,7 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="js-notebook-viewer-mounted container-fluid md prepend-top-default append-bottom-default"
|
||||
>
|
||||
<div class="js-notebook-viewer-mounted container-fluid md prepend-top-default gl-mb-3">
|
||||
<div v-if="loading && !error" class="text-center loading">
|
||||
<gl-loading-icon class="mt-5" size="lg" />
|
||||
</div>
|
||||
|
|
|
@ -34,7 +34,7 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div class="js-pdf-viewer container-fluid md prepend-top-default append-bottom-default">
|
||||
<div class="js-pdf-viewer container-fluid md prepend-top-default gl-mb-3">
|
||||
<div v-if="loading && !error" class="text-center loading">
|
||||
<gl-loading-icon class="mt-5" size="lg" />
|
||||
</div>
|
||||
|
|
|
@ -56,7 +56,7 @@ export default class SketchLoader {
|
|||
error() {
|
||||
const errorMsg = document.createElement('p');
|
||||
|
||||
errorMsg.className = 'prepend-top-default append-bottom-default text-center';
|
||||
errorMsg.className = 'prepend-top-default gl-mb-3 text-center';
|
||||
errorMsg.textContent = __(`
|
||||
Cannot show preview. For previews on sketch files, they must have the file format
|
||||
introduced by Sketch version 43 and above.
|
||||
|
|
|
@ -115,7 +115,7 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div class="append-bottom-default deploy-keys">
|
||||
<div class="gl-mb-3 deploy-keys">
|
||||
<gl-loading-icon
|
||||
v-if="isLoading && !hasKeys"
|
||||
:label="s__('DeployKeys|Loading deploy keys')"
|
||||
|
|
|
@ -398,7 +398,7 @@ export default {
|
|||
class="files d-flex"
|
||||
>
|
||||
<div
|
||||
v-show="showTreeList"
|
||||
v-if="showTreeList"
|
||||
:style="{ width: `${treeWidth}px` }"
|
||||
class="diff-tree-list js-diff-tree-list mr-3"
|
||||
>
|
||||
|
|
|
@ -84,7 +84,7 @@ export default {
|
|||
v-for="(suggestion, index) in issues"
|
||||
:key="suggestion.id"
|
||||
:class="{
|
||||
'append-bottom-default': index !== issues.length - 1,
|
||||
'gl-mb-3': index !== issues.length - 1,
|
||||
}"
|
||||
>
|
||||
<suggestion :suggestion="suggestion" />
|
||||
|
|
|
@ -63,7 +63,7 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div class="prepend-top-default append-bottom-default clearfix">
|
||||
<div class="prepend-top-default gl-mb-3 clearfix">
|
||||
<button
|
||||
:class="{ disabled: formState.updateLoading || !isSubmitEnabled }"
|
||||
:disabled="formState.updateLoading || !isSubmitEnabled"
|
||||
|
|
|
@ -274,7 +274,7 @@ export default {
|
|||
};
|
||||
</script>
|
||||
<template>
|
||||
<div class="prepend-top-default append-bottom-default js-environment-container">
|
||||
<div class="prepend-top-default gl-mb-3 js-environment-container">
|
||||
<div class="environment-information">
|
||||
<ci-icon :status="iconStatus" />
|
||||
<p class="inline gl-mb-0" v-html="environment"></p>
|
||||
|
|
|
@ -108,7 +108,7 @@ export default {
|
|||
/>
|
||||
</ci-header>
|
||||
|
||||
<gl-loading-icon v-if="isLoading" size="lg" class="prepend-top-default append-bottom-default" />
|
||||
<gl-loading-icon v-if="isLoading" size="lg" class="prepend-top-default gl-mb-3" />
|
||||
|
||||
<gl-modal
|
||||
:modal-id="$options.DELETE_MODAL_ID"
|
||||
|
|
|
@ -42,7 +42,7 @@ export default {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="hasSuites" class="test-reports-table append-bottom-default js-test-cases-table">
|
||||
<div v-if="hasSuites" class="test-reports-table gl-mb-3 js-test-cases-table">
|
||||
<div role="row" class="gl-responsive-table-row table-row-header font-weight-bold fgray">
|
||||
<div role="rowheader" class="table-section section-20">
|
||||
{{ __('Class') }}
|
||||
|
|
|
@ -46,7 +46,7 @@ export default {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="hasSuites" class="test-reports-table append-bottom-default js-test-suites-table">
|
||||
<div v-if="hasSuites" class="test-reports-table gl-mb-3 js-test-suites-table">
|
||||
<div role="row" class="gl-responsive-table-row table-row-header font-weight-bold">
|
||||
<div role="rowheader" class="table-section section-25 pl-3">
|
||||
{{ __('Suite') }}
|
||||
|
|
|
@ -71,7 +71,7 @@ export default {
|
|||
<template>
|
||||
<section id="serverless-function-details">
|
||||
<h3 class="serverless-function-name">{{ name }}</h3>
|
||||
<div class="append-bottom-default serverless-function-description">
|
||||
<div class="gl-mb-3 serverless-function-description">
|
||||
<div v-for="(line, index) in description.split('\n')" :key="index">{{ line }}</div>
|
||||
</div>
|
||||
<url :uri="funcUrl" />
|
||||
|
|
|
@ -75,11 +75,7 @@ export default {
|
|||
|
||||
<template>
|
||||
<section id="serverless-functions" class="flex-grow">
|
||||
<gl-loading-icon
|
||||
v-if="checkingInstalled"
|
||||
size="lg"
|
||||
class="prepend-top-default append-bottom-default"
|
||||
/>
|
||||
<gl-loading-icon v-if="checkingInstalled" size="lg" class="prepend-top-default gl-mb-3" />
|
||||
|
||||
<div v-else-if="isInstalled">
|
||||
<div v-if="hasFunctionData">
|
||||
|
@ -98,7 +94,7 @@ export default {
|
|||
<gl-loading-icon
|
||||
v-if="isLoading"
|
||||
size="lg"
|
||||
class="prepend-top-default append-bottom-default js-functions-loader"
|
||||
class="prepend-top-default gl-mb-3 js-functions-loader"
|
||||
/>
|
||||
</div>
|
||||
<div v-else class="empty-state js-empty-state">
|
||||
|
|
|
@ -77,7 +77,7 @@ export default {
|
|||
};
|
||||
</script>
|
||||
<template>
|
||||
<div class="d-flex mr-source-target append-bottom-default">
|
||||
<div class="d-flex mr-source-target gl-mb-3">
|
||||
<mr-widget-icon name="git-merge" />
|
||||
<div class="git-merge-container d-flex">
|
||||
<div class="normal">
|
||||
|
|
|
@ -34,7 +34,7 @@ export default {
|
|||
};
|
||||
</script>
|
||||
<template>
|
||||
<div :id="$options.popoverContainer" class="d-flex mr-pipeline-suggest append-bottom-default">
|
||||
<div :id="$options.popoverContainer" class="d-flex mr-pipeline-suggest gl-mb-3">
|
||||
<mr-widget-icon :name="$options.iconName" />
|
||||
<div :id="$options.popoverTarget">
|
||||
<gl-sprintf
|
||||
|
|
|
@ -29,7 +29,7 @@ export default {
|
|||
<textarea
|
||||
:id="inputId"
|
||||
:value="value"
|
||||
class="form-control js-gfm-input append-bottom-default commit-message-edit"
|
||||
class="form-control js-gfm-input gl-mb-3 commit-message-edit"
|
||||
dir="auto"
|
||||
required="required"
|
||||
rows="7"
|
||||
|
|
|
@ -231,7 +231,7 @@ export default {
|
|||
<template>
|
||||
<div
|
||||
ref="gl-form"
|
||||
:class="{ 'prepend-top-default append-bottom-default': addSpacingClasses }"
|
||||
:class="{ 'prepend-top-default gl-mb-3': addSpacingClasses }"
|
||||
class="js-vue-markdown-field md-area position-relative"
|
||||
>
|
||||
<markdown-header
|
||||
|
|
|
@ -89,14 +89,13 @@ export default {
|
|||
<div class="md-header">
|
||||
<ul class="nav-links clearfix">
|
||||
<li :class="{ active: !previewMarkdown }" class="md-header-tab">
|
||||
<button class="js-write-link" tabindex="-1" type="button" @click="writeMarkdownTab($event)">
|
||||
<button class="js-write-link" type="button" @click="writeMarkdownTab($event)">
|
||||
{{ __('Write') }}
|
||||
</button>
|
||||
</li>
|
||||
<li :class="{ active: previewMarkdown }" class="md-header-tab">
|
||||
<button
|
||||
class="js-preview-link js-md-preview-button"
|
||||
tabindex="-1"
|
||||
type="button"
|
||||
@click="previewMarkdownTab($event)"
|
||||
>
|
||||
|
|
|
@ -64,7 +64,6 @@ export default {
|
|||
:aria-label="buttonTitle"
|
||||
type="button"
|
||||
class="toolbar-btn js-md"
|
||||
tabindex="-1"
|
||||
data-container="body"
|
||||
@click="() => $emit('click')"
|
||||
>
|
||||
|
|
|
@ -421,7 +421,6 @@ img.emoji {
|
|||
.append-bottom-10 { margin-bottom: 10px; }
|
||||
.append-bottom-15 { margin-bottom: 15px; }
|
||||
.append-bottom-20 { margin-bottom: 20px; }
|
||||
.append-bottom-default { margin-bottom: $gl-padding; }
|
||||
.prepend-bottom-32 { margin-bottom: 32px; }
|
||||
.ml-10 { margin-left: 4.5rem; }
|
||||
.inline { display: inline-block; }
|
||||
|
|
|
@ -10,7 +10,7 @@ module KnownSignIn
|
|||
private
|
||||
|
||||
def verify_known_sign_in
|
||||
return unless current_user
|
||||
return unless Gitlab::CurrentSettings.notify_on_unknown_sign_in? && current_user
|
||||
|
||||
notify_user unless known_device? || known_remote_ip?
|
||||
|
||||
|
|
|
@ -3,11 +3,14 @@
|
|||
class Dashboard::TodosController < Dashboard::ApplicationController
|
||||
include ActionView::Helpers::NumberHelper
|
||||
include PaginatedCollection
|
||||
include Analytics::UniqueVisitsHelper
|
||||
|
||||
before_action :authorize_read_project!, only: :index
|
||||
before_action :authorize_read_group!, only: :index
|
||||
before_action :find_todos, only: [:index, :destroy_all]
|
||||
|
||||
track_unique_visits :index, target_id: 'u_analytics_todos'
|
||||
|
||||
def index
|
||||
@sort = params[:sort]
|
||||
@todos = @todos.page(params[:page])
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class InstanceStatistics::CohortsController < InstanceStatistics::ApplicationController
|
||||
include Analytics::UniqueVisitsHelper
|
||||
|
||||
before_action :authenticate_usage_ping_enabled_or_admin!
|
||||
|
||||
track_unique_visits :index, target_id: 'i_analytics_cohorts'
|
||||
|
||||
def index
|
||||
if Gitlab::CurrentSettings.usage_ping_enabled
|
||||
cohorts_results = Rails.cache.fetch('cohorts', expires_in: 1.day) do
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class InstanceStatistics::DevOpsScoreController < InstanceStatistics::ApplicationController
|
||||
include Analytics::UniqueVisitsHelper
|
||||
|
||||
track_unique_visits :index, target_id: 'i_analytics_dev_ops_score'
|
||||
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
def index
|
||||
@metric = DevOpsScore::Metric.order(:created_at).last&.present
|
||||
|
|
|
@ -4,10 +4,13 @@ class Projects::CycleAnalyticsController < Projects::ApplicationController
|
|||
include ActionView::Helpers::DateHelper
|
||||
include ActionView::Helpers::TextHelper
|
||||
include CycleAnalyticsParams
|
||||
include Analytics::UniqueVisitsHelper
|
||||
|
||||
before_action :whitelist_query_limiting, only: [:show]
|
||||
before_action :authorize_read_cycle_analytics!
|
||||
|
||||
track_unique_visits :show, target_id: 'p_analytics_valuestream'
|
||||
|
||||
def show
|
||||
@cycle_analytics = ::CycleAnalytics::ProjectLevel.new(@project, options: options(cycle_analytics_project_params))
|
||||
|
||||
|
|
|
@ -2,12 +2,15 @@
|
|||
|
||||
class Projects::GraphsController < Projects::ApplicationController
|
||||
include ExtractsPath
|
||||
include Analytics::UniqueVisitsHelper
|
||||
|
||||
# Authorize
|
||||
before_action :require_non_empty_project
|
||||
before_action :assign_ref_vars
|
||||
before_action :authorize_read_repository_graphs!
|
||||
|
||||
track_unique_visits :charts, target_id: 'p_analytics_repo'
|
||||
|
||||
def show
|
||||
respond_to do |format|
|
||||
format.html
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
class Projects::PipelinesController < Projects::ApplicationController
|
||||
include ::Gitlab::Utils::StrongMemoize
|
||||
include Analytics::UniqueVisitsHelper
|
||||
|
||||
before_action :whitelist_query_limiting, only: [:create, :retry]
|
||||
before_action :pipeline, except: [:index, :new, :create, :charts]
|
||||
|
@ -20,6 +21,8 @@ class Projects::PipelinesController < Projects::ApplicationController
|
|||
|
||||
around_action :allow_gitaly_ref_name_caching, only: [:index, :show]
|
||||
|
||||
track_unique_visits :charts, target_id: 'p_analytics_pipelines'
|
||||
|
||||
wrap_parameters Ci::Pipeline
|
||||
|
||||
POLLING_INTERVAL = 10_000
|
||||
|
|
|
@ -71,7 +71,7 @@ module Ci
|
|||
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
def by_status(items)
|
||||
return items unless HasStatus::AVAILABLE_STATUSES.include?(params[:status])
|
||||
return items unless Ci::HasStatus::AVAILABLE_STATUSES.include?(params[:status])
|
||||
|
||||
items.where(status: params[:status])
|
||||
end
|
||||
|
|
|
@ -21,7 +21,7 @@ module Ci
|
|||
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
def by_status(items)
|
||||
return items unless HasStatus::AVAILABLE_STATUSES.include?(params[:status])
|
||||
return items unless Ci::HasStatus::AVAILABLE_STATUSES.include?(params[:status])
|
||||
|
||||
items.where(status: params[:status])
|
||||
end
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Analytics
|
||||
module UniqueVisitsHelper
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def visitor_id
|
||||
return cookies[:visitor_id] if cookies[:visitor_id].present?
|
||||
return unless current_user
|
||||
|
||||
uuid = SecureRandom.uuid
|
||||
cookies[:visitor_id] = { value: uuid, expires: 24.months }
|
||||
uuid
|
||||
end
|
||||
|
||||
def track_visit(target_id)
|
||||
return unless Feature.enabled?(:track_unique_visits)
|
||||
return unless Gitlab::CurrentSettings.usage_ping_enabled?
|
||||
return unless visitor_id
|
||||
|
||||
Gitlab::Analytics::UniqueVisits.new.track_visit(visitor_id, target_id)
|
||||
end
|
||||
|
||||
class_methods do
|
||||
def track_unique_visits(controller_actions, target_id:)
|
||||
after_action only: controller_actions, if: -> { request.format.html? && request.headers['DNT'] != '1' } do
|
||||
track_visit(target_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -244,6 +244,7 @@ module ApplicationSettingsHelper
|
|||
:metrics_method_call_threshold,
|
||||
:minimum_password_length,
|
||||
:mirror_available,
|
||||
:notify_on_unknown_sign_in,
|
||||
:pages_domain_verification_enabled,
|
||||
:password_authentication_enabled_for_web,
|
||||
:password_authentication_enabled_for_git,
|
||||
|
|
|
@ -244,7 +244,6 @@ module MarkupHelper
|
|||
content_tag :button,
|
||||
type: 'button',
|
||||
class: 'toolbar-btn js-md has-tooltip',
|
||||
tabindex: -1,
|
||||
data: data,
|
||||
title: options[:title],
|
||||
aria: { label: options[:title] } do
|
||||
|
|
|
@ -88,6 +88,7 @@ module ApplicationSettingImplementation
|
|||
max_attachment_size: Settings.gitlab['max_attachment_size'],
|
||||
max_import_size: 50,
|
||||
mirror_available: true,
|
||||
notify_on_unknown_sign_in: true,
|
||||
outbound_local_requests_whitelist: [],
|
||||
password_authentication_enabled_for_git: true,
|
||||
password_authentication_enabled_for_web: Settings.gitlab['signin_enabled'],
|
||||
|
|
|
@ -16,6 +16,7 @@ class AuditEvent < ApplicationRecord
|
|||
|
||||
scope :by_entity_type, -> (entity_type) { where(entity_type: entity_type) }
|
||||
scope :by_entity_id, -> (entity_id) { where(entity_id: entity_id) }
|
||||
scope :by_author_id, -> (author_id) { where(author_id: author_id) }
|
||||
|
||||
after_initialize :initialize_details
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
module Ci
|
||||
class Pipeline < ApplicationRecord
|
||||
extend Gitlab::Ci::Model
|
||||
include HasStatus
|
||||
include Ci::HasStatus
|
||||
include Importable
|
||||
include AfterCommitQueue
|
||||
include Presentable
|
||||
|
@ -640,7 +640,7 @@ module Ci
|
|||
when 'manual' then block
|
||||
when 'scheduled' then delay
|
||||
else
|
||||
raise HasStatus::UnknownStatusError,
|
||||
raise Ci::HasStatus::UnknownStatusError,
|
||||
"Unknown status `#{new_status}`"
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,10 +4,10 @@ module Ci
|
|||
class Stage < ApplicationRecord
|
||||
extend Gitlab::Ci::Model
|
||||
include Importable
|
||||
include HasStatus
|
||||
include Ci::HasStatus
|
||||
include Gitlab::OptimisticLocking
|
||||
|
||||
enum status: HasStatus::STATUSES_ENUM
|
||||
enum status: Ci::HasStatus::STATUSES_ENUM
|
||||
|
||||
belongs_to :project
|
||||
belongs_to :pipeline
|
||||
|
@ -98,7 +98,7 @@ module Ci
|
|||
when 'scheduled' then delay
|
||||
when 'skipped', nil then skip
|
||||
else
|
||||
raise HasStatus::UnknownStatusError,
|
||||
raise Ci::HasStatus::UnknownStatusError,
|
||||
"Unknown status `#{new_status}`"
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CommitStatus < ApplicationRecord
|
||||
include HasStatus
|
||||
include Ci::HasStatus
|
||||
include Importable
|
||||
include AfterCommitQueue
|
||||
include Presentable
|
||||
|
|
|
@ -0,0 +1,168 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Ci
|
||||
module HasStatus
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
DEFAULT_STATUS = 'created'
|
||||
BLOCKED_STATUS = %w[manual scheduled].freeze
|
||||
AVAILABLE_STATUSES = %w[created waiting_for_resource preparing pending running success failed canceled skipped manual scheduled].freeze
|
||||
STARTED_STATUSES = %w[running success failed skipped manual scheduled].freeze
|
||||
ACTIVE_STATUSES = %w[waiting_for_resource preparing pending running].freeze
|
||||
COMPLETED_STATUSES = %w[success failed canceled skipped].freeze
|
||||
ORDERED_STATUSES = %w[failed preparing pending running waiting_for_resource manual scheduled canceled success skipped created].freeze
|
||||
PASSED_WITH_WARNINGS_STATUSES = %w[failed canceled].to_set.freeze
|
||||
EXCLUDE_IGNORED_STATUSES = %w[manual failed canceled].to_set.freeze
|
||||
STATUSES_ENUM = { created: 0, pending: 1, running: 2, success: 3,
|
||||
failed: 4, canceled: 5, skipped: 6, manual: 7,
|
||||
scheduled: 8, preparing: 9, waiting_for_resource: 10 }.freeze
|
||||
|
||||
UnknownStatusError = Class.new(StandardError)
|
||||
|
||||
class_methods do
|
||||
def legacy_status_sql
|
||||
scope_relevant = respond_to?(:exclude_ignored) ? exclude_ignored : all
|
||||
scope_warnings = respond_to?(:failed_but_allowed) ? failed_but_allowed : none
|
||||
|
||||
builds = scope_relevant.select('count(*)').to_sql
|
||||
created = scope_relevant.created.select('count(*)').to_sql
|
||||
success = scope_relevant.success.select('count(*)').to_sql
|
||||
manual = scope_relevant.manual.select('count(*)').to_sql
|
||||
scheduled = scope_relevant.scheduled.select('count(*)').to_sql
|
||||
preparing = scope_relevant.preparing.select('count(*)').to_sql
|
||||
waiting_for_resource = scope_relevant.waiting_for_resource.select('count(*)').to_sql
|
||||
pending = scope_relevant.pending.select('count(*)').to_sql
|
||||
running = scope_relevant.running.select('count(*)').to_sql
|
||||
skipped = scope_relevant.skipped.select('count(*)').to_sql
|
||||
canceled = scope_relevant.canceled.select('count(*)').to_sql
|
||||
warnings = scope_warnings.select('count(*) > 0').to_sql.presence || 'false'
|
||||
|
||||
Arel.sql(
|
||||
"(CASE
|
||||
WHEN (#{builds})=(#{skipped}) AND (#{warnings}) THEN 'success'
|
||||
WHEN (#{builds})=(#{skipped}) THEN 'skipped'
|
||||
WHEN (#{builds})=(#{success}) THEN 'success'
|
||||
WHEN (#{builds})=(#{created}) THEN 'created'
|
||||
WHEN (#{builds})=(#{preparing}) THEN 'preparing'
|
||||
WHEN (#{builds})=(#{success})+(#{skipped}) THEN 'success'
|
||||
WHEN (#{builds})=(#{success})+(#{skipped})+(#{canceled}) THEN 'canceled'
|
||||
WHEN (#{builds})=(#{created})+(#{skipped})+(#{pending}) THEN 'pending'
|
||||
WHEN (#{running})+(#{pending})>0 THEN 'running'
|
||||
WHEN (#{waiting_for_resource})>0 THEN 'waiting_for_resource'
|
||||
WHEN (#{manual})>0 THEN 'manual'
|
||||
WHEN (#{scheduled})>0 THEN 'scheduled'
|
||||
WHEN (#{preparing})>0 THEN 'preparing'
|
||||
WHEN (#{created})>0 THEN 'running'
|
||||
ELSE 'failed'
|
||||
END)"
|
||||
)
|
||||
end
|
||||
|
||||
def legacy_status
|
||||
all.pluck(legacy_status_sql).first
|
||||
end
|
||||
|
||||
# This method should not be used.
|
||||
# This method performs expensive calculation of status:
|
||||
# 1. By plucking all related objects,
|
||||
# 2. Or executes expensive SQL query
|
||||
def slow_composite_status(project:)
|
||||
if ::Gitlab::Ci::Features.composite_status?(project)
|
||||
Gitlab::Ci::Status::Composite
|
||||
.new(all, with_allow_failure: columns_hash.key?('allow_failure'))
|
||||
.status
|
||||
else
|
||||
legacy_status
|
||||
end
|
||||
end
|
||||
|
||||
def started_at
|
||||
all.minimum(:started_at)
|
||||
end
|
||||
|
||||
def finished_at
|
||||
all.maximum(:finished_at)
|
||||
end
|
||||
|
||||
def all_state_names
|
||||
state_machines.values.flat_map(&:states).flat_map { |s| s.map(&:name) }
|
||||
end
|
||||
|
||||
def completed_statuses
|
||||
COMPLETED_STATUSES.map(&:to_sym)
|
||||
end
|
||||
end
|
||||
|
||||
included do
|
||||
validates :status, inclusion: { in: AVAILABLE_STATUSES }
|
||||
|
||||
state_machine :status, initial: :created do
|
||||
state :created, value: 'created'
|
||||
state :waiting_for_resource, value: 'waiting_for_resource'
|
||||
state :preparing, value: 'preparing'
|
||||
state :pending, value: 'pending'
|
||||
state :running, value: 'running'
|
||||
state :failed, value: 'failed'
|
||||
state :success, value: 'success'
|
||||
state :canceled, value: 'canceled'
|
||||
state :skipped, value: 'skipped'
|
||||
state :manual, value: 'manual'
|
||||
state :scheduled, value: 'scheduled'
|
||||
end
|
||||
|
||||
scope :created, -> { with_status(:created) }
|
||||
scope :waiting_for_resource, -> { with_status(:waiting_for_resource) }
|
||||
scope :preparing, -> { with_status(:preparing) }
|
||||
scope :relevant, -> { without_status(:created) }
|
||||
scope :running, -> { with_status(:running) }
|
||||
scope :pending, -> { with_status(:pending) }
|
||||
scope :success, -> { with_status(:success) }
|
||||
scope :failed, -> { with_status(:failed) }
|
||||
scope :canceled, -> { with_status(:canceled) }
|
||||
scope :skipped, -> { with_status(:skipped) }
|
||||
scope :manual, -> { with_status(:manual) }
|
||||
scope :scheduled, -> { with_status(:scheduled) }
|
||||
scope :alive, -> { with_status(:created, :waiting_for_resource, :preparing, :pending, :running) }
|
||||
scope :alive_or_scheduled, -> { with_status(:created, :waiting_for_resource, :preparing, :pending, :running, :scheduled) }
|
||||
scope :created_or_pending, -> { with_status(:created, :pending) }
|
||||
scope :running_or_pending, -> { with_status(:running, :pending) }
|
||||
scope :finished, -> { with_status(:success, :failed, :canceled) }
|
||||
scope :failed_or_canceled, -> { with_status(:failed, :canceled) }
|
||||
scope :incomplete, -> { without_statuses(completed_statuses) }
|
||||
|
||||
scope :cancelable, -> do
|
||||
where(status: [:running, :waiting_for_resource, :preparing, :pending, :created, :scheduled])
|
||||
end
|
||||
|
||||
scope :without_statuses, -> (names) do
|
||||
with_status(all_state_names - names.to_a)
|
||||
end
|
||||
end
|
||||
|
||||
def started?
|
||||
STARTED_STATUSES.include?(status) && started_at
|
||||
end
|
||||
|
||||
def active?
|
||||
ACTIVE_STATUSES.include?(status)
|
||||
end
|
||||
|
||||
def complete?
|
||||
COMPLETED_STATUSES.include?(status)
|
||||
end
|
||||
|
||||
def blocked?
|
||||
BLOCKED_STATUS.include?(status)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def calculate_duration
|
||||
if started_at && finished_at
|
||||
finished_at - started_at
|
||||
elsif started_at
|
||||
Time.current - started_at
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,166 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module HasStatus
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
DEFAULT_STATUS = 'created'
|
||||
BLOCKED_STATUS = %w[manual scheduled].freeze
|
||||
AVAILABLE_STATUSES = %w[created waiting_for_resource preparing pending running success failed canceled skipped manual scheduled].freeze
|
||||
STARTED_STATUSES = %w[running success failed skipped manual scheduled].freeze
|
||||
ACTIVE_STATUSES = %w[waiting_for_resource preparing pending running].freeze
|
||||
COMPLETED_STATUSES = %w[success failed canceled skipped].freeze
|
||||
ORDERED_STATUSES = %w[failed preparing pending running waiting_for_resource manual scheduled canceled success skipped created].freeze
|
||||
PASSED_WITH_WARNINGS_STATUSES = %w[failed canceled].to_set.freeze
|
||||
EXCLUDE_IGNORED_STATUSES = %w[manual failed canceled].to_set.freeze
|
||||
STATUSES_ENUM = { created: 0, pending: 1, running: 2, success: 3,
|
||||
failed: 4, canceled: 5, skipped: 6, manual: 7,
|
||||
scheduled: 8, preparing: 9, waiting_for_resource: 10 }.freeze
|
||||
|
||||
UnknownStatusError = Class.new(StandardError)
|
||||
|
||||
class_methods do
|
||||
def legacy_status_sql
|
||||
scope_relevant = respond_to?(:exclude_ignored) ? exclude_ignored : all
|
||||
scope_warnings = respond_to?(:failed_but_allowed) ? failed_but_allowed : none
|
||||
|
||||
builds = scope_relevant.select('count(*)').to_sql
|
||||
created = scope_relevant.created.select('count(*)').to_sql
|
||||
success = scope_relevant.success.select('count(*)').to_sql
|
||||
manual = scope_relevant.manual.select('count(*)').to_sql
|
||||
scheduled = scope_relevant.scheduled.select('count(*)').to_sql
|
||||
preparing = scope_relevant.preparing.select('count(*)').to_sql
|
||||
waiting_for_resource = scope_relevant.waiting_for_resource.select('count(*)').to_sql
|
||||
pending = scope_relevant.pending.select('count(*)').to_sql
|
||||
running = scope_relevant.running.select('count(*)').to_sql
|
||||
skipped = scope_relevant.skipped.select('count(*)').to_sql
|
||||
canceled = scope_relevant.canceled.select('count(*)').to_sql
|
||||
warnings = scope_warnings.select('count(*) > 0').to_sql.presence || 'false'
|
||||
|
||||
Arel.sql(
|
||||
"(CASE
|
||||
WHEN (#{builds})=(#{skipped}) AND (#{warnings}) THEN 'success'
|
||||
WHEN (#{builds})=(#{skipped}) THEN 'skipped'
|
||||
WHEN (#{builds})=(#{success}) THEN 'success'
|
||||
WHEN (#{builds})=(#{created}) THEN 'created'
|
||||
WHEN (#{builds})=(#{preparing}) THEN 'preparing'
|
||||
WHEN (#{builds})=(#{success})+(#{skipped}) THEN 'success'
|
||||
WHEN (#{builds})=(#{success})+(#{skipped})+(#{canceled}) THEN 'canceled'
|
||||
WHEN (#{builds})=(#{created})+(#{skipped})+(#{pending}) THEN 'pending'
|
||||
WHEN (#{running})+(#{pending})>0 THEN 'running'
|
||||
WHEN (#{waiting_for_resource})>0 THEN 'waiting_for_resource'
|
||||
WHEN (#{manual})>0 THEN 'manual'
|
||||
WHEN (#{scheduled})>0 THEN 'scheduled'
|
||||
WHEN (#{preparing})>0 THEN 'preparing'
|
||||
WHEN (#{created})>0 THEN 'running'
|
||||
ELSE 'failed'
|
||||
END)"
|
||||
)
|
||||
end
|
||||
|
||||
def legacy_status
|
||||
all.pluck(legacy_status_sql).first
|
||||
end
|
||||
|
||||
# This method should not be used.
|
||||
# This method performs expensive calculation of status:
|
||||
# 1. By plucking all related objects,
|
||||
# 2. Or executes expensive SQL query
|
||||
def slow_composite_status(project:)
|
||||
if ::Gitlab::Ci::Features.composite_status?(project)
|
||||
Gitlab::Ci::Status::Composite
|
||||
.new(all, with_allow_failure: columns_hash.key?('allow_failure'))
|
||||
.status
|
||||
else
|
||||
legacy_status
|
||||
end
|
||||
end
|
||||
|
||||
def started_at
|
||||
all.minimum(:started_at)
|
||||
end
|
||||
|
||||
def finished_at
|
||||
all.maximum(:finished_at)
|
||||
end
|
||||
|
||||
def all_state_names
|
||||
state_machines.values.flat_map(&:states).flat_map { |s| s.map(&:name) }
|
||||
end
|
||||
|
||||
def completed_statuses
|
||||
COMPLETED_STATUSES.map(&:to_sym)
|
||||
end
|
||||
end
|
||||
|
||||
included do
|
||||
validates :status, inclusion: { in: AVAILABLE_STATUSES }
|
||||
|
||||
state_machine :status, initial: :created do
|
||||
state :created, value: 'created'
|
||||
state :waiting_for_resource, value: 'waiting_for_resource'
|
||||
state :preparing, value: 'preparing'
|
||||
state :pending, value: 'pending'
|
||||
state :running, value: 'running'
|
||||
state :failed, value: 'failed'
|
||||
state :success, value: 'success'
|
||||
state :canceled, value: 'canceled'
|
||||
state :skipped, value: 'skipped'
|
||||
state :manual, value: 'manual'
|
||||
state :scheduled, value: 'scheduled'
|
||||
end
|
||||
|
||||
scope :created, -> { with_status(:created) }
|
||||
scope :waiting_for_resource, -> { with_status(:waiting_for_resource) }
|
||||
scope :preparing, -> { with_status(:preparing) }
|
||||
scope :relevant, -> { without_status(:created) }
|
||||
scope :running, -> { with_status(:running) }
|
||||
scope :pending, -> { with_status(:pending) }
|
||||
scope :success, -> { with_status(:success) }
|
||||
scope :failed, -> { with_status(:failed) }
|
||||
scope :canceled, -> { with_status(:canceled) }
|
||||
scope :skipped, -> { with_status(:skipped) }
|
||||
scope :manual, -> { with_status(:manual) }
|
||||
scope :scheduled, -> { with_status(:scheduled) }
|
||||
scope :alive, -> { with_status(:created, :waiting_for_resource, :preparing, :pending, :running) }
|
||||
scope :alive_or_scheduled, -> { with_status(:created, :waiting_for_resource, :preparing, :pending, :running, :scheduled) }
|
||||
scope :created_or_pending, -> { with_status(:created, :pending) }
|
||||
scope :running_or_pending, -> { with_status(:running, :pending) }
|
||||
scope :finished, -> { with_status(:success, :failed, :canceled) }
|
||||
scope :failed_or_canceled, -> { with_status(:failed, :canceled) }
|
||||
scope :incomplete, -> { without_statuses(completed_statuses) }
|
||||
|
||||
scope :cancelable, -> do
|
||||
where(status: [:running, :waiting_for_resource, :preparing, :pending, :created, :scheduled])
|
||||
end
|
||||
|
||||
scope :without_statuses, -> (names) do
|
||||
with_status(all_state_names - names.to_a)
|
||||
end
|
||||
end
|
||||
|
||||
def started?
|
||||
STARTED_STATUSES.include?(status) && started_at
|
||||
end
|
||||
|
||||
def active?
|
||||
ACTIVE_STATUSES.include?(status)
|
||||
end
|
||||
|
||||
def complete?
|
||||
COMPLETED_STATUSES.include?(status)
|
||||
end
|
||||
|
||||
def blocked?
|
||||
BLOCKED_STATUS.include?(status)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def calculate_duration
|
||||
if started_at && finished_at
|
||||
finished_at - started_at
|
||||
elsif started_at
|
||||
Time.current - started_at
|
||||
end
|
||||
end
|
||||
end
|
|
@ -147,7 +147,7 @@ class Environment < ApplicationRecord
|
|||
Ci::Build.joins(inner_join_stop_actions)
|
||||
.with(cte.to_arel)
|
||||
.where(ci_builds[:commit_id].in(pipeline_ids))
|
||||
.where(status: HasStatus::BLOCKED_STATUS)
|
||||
.where(status: Ci::HasStatus::BLOCKED_STATUS)
|
||||
.preload_project_and_pipeline_project
|
||||
.preload(:user, :metadata, :deployment)
|
||||
end
|
||||
|
|
|
@ -2414,6 +2414,11 @@ class Project < ApplicationRecord
|
|||
super || build_metrics_setting
|
||||
end
|
||||
|
||||
def service_desk_enabled
|
||||
false
|
||||
end
|
||||
alias_method :service_desk_enabled?, :service_desk_enabled
|
||||
|
||||
private
|
||||
|
||||
def find_service(services, name)
|
||||
|
|
|
@ -656,6 +656,15 @@ class User < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
def support_bot
|
||||
email_pattern = "support%s@#{Settings.gitlab.host}"
|
||||
|
||||
unique_internal(where(user_type: :support_bot), 'support-bot', email_pattern) do |u|
|
||||
u.bio = 'The GitLab support bot used for Service Desk'
|
||||
u.name = 'GitLab Support Bot'
|
||||
end
|
||||
end
|
||||
|
||||
# Return true if there is only single non-internal user in the deployment,
|
||||
# ghost user is ignored.
|
||||
def single_user?
|
||||
|
|
|
@ -21,6 +21,10 @@ class BasePolicy < DeclarativePolicy::Base
|
|||
with_options scope: :user, score: 0
|
||||
condition(:deactivated) { @user&.deactivated? }
|
||||
|
||||
desc "User is support bot"
|
||||
with_options scope: :user, score: 0
|
||||
condition(:support_bot) { @user&.support_bot? }
|
||||
|
||||
desc "User email is unconfirmed or user account is locked"
|
||||
with_options scope: :user, score: 0
|
||||
condition(:inactive) do
|
||||
|
|
|
@ -45,6 +45,10 @@ module PolicyActor
|
|||
false
|
||||
end
|
||||
|
||||
def support_bot?
|
||||
false
|
||||
end
|
||||
|
||||
def deactivated?
|
||||
false
|
||||
end
|
||||
|
|
|
@ -123,6 +123,9 @@ class ProjectPolicy < BasePolicy
|
|||
!@subject.design_management_enabled?
|
||||
end
|
||||
|
||||
with_scope :subject
|
||||
condition(:service_desk_enabled) { @subject.service_desk_enabled? }
|
||||
|
||||
# We aren't checking `:read_issue` or `:read_merge_request` in this case
|
||||
# because it could be possible for a user to see an issuable-iid
|
||||
# (`:read_issue_iid` or `:read_merge_request_iid`) but then wouldn't be
|
||||
|
@ -578,6 +581,12 @@ class ProjectPolicy < BasePolicy
|
|||
enable :read_build_report_results
|
||||
end
|
||||
|
||||
rule { support_bot }.enable :guest_access
|
||||
rule { support_bot & ~service_desk_enabled }.policy do
|
||||
prevent :create_note
|
||||
prevent :read_project
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def team_member?
|
||||
|
@ -626,6 +635,7 @@ class ProjectPolicy < BasePolicy
|
|||
|
||||
def lookup_access_level!
|
||||
return ::Gitlab::Access::REPORTER if alert_bot?
|
||||
return ::Gitlab::Access::REPORTER if support_bot? && service_desk_enabled?
|
||||
|
||||
# NOTE: max_member_access has its own cache
|
||||
project.team.max_member_access(@user.id)
|
||||
|
|
|
@ -59,13 +59,13 @@ class StageEntity < Grape::Entity
|
|||
end
|
||||
|
||||
def latest_statuses
|
||||
HasStatus::ORDERED_STATUSES.flat_map do |ordered_status|
|
||||
Ci::HasStatus::ORDERED_STATUSES.flat_map do |ordered_status|
|
||||
grouped_statuses.fetch(ordered_status, [])
|
||||
end
|
||||
end
|
||||
|
||||
def retried_statuses
|
||||
HasStatus::ORDERED_STATUSES.flat_map do |ordered_status|
|
||||
Ci::HasStatus::ORDERED_STATUSES.flat_map do |ordered_status|
|
||||
grouped_retried_statuses.fetch(ordered_status, [])
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module AuthorizedProjectUpdate
|
||||
class PeriodicRecalculateService
|
||||
BATCH_SIZE = 480
|
||||
DELAY_INTERVAL = 30.seconds.to_i
|
||||
|
||||
def execute
|
||||
# Using this approach (instead of eg. User.each_batch) keeps the arguments
|
||||
# the same for AuthorizedProjectUpdate::UserRefreshOverUserRangeWorker
|
||||
# even if the user list changes, so we can deduplicate these jobs.
|
||||
(1..User.maximum(:id)).each_slice(BATCH_SIZE).with_index do |batch, index|
|
||||
delay = DELAY_INTERVAL * index
|
||||
AuthorizedProjectUpdate::UserRefreshOverUserRangeWorker.perform_in(delay, *batch.minmax)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,20 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module AuthorizedProjectUpdate
|
||||
class RecalculateForUserRangeService
|
||||
def initialize(start_user_id, end_user_id)
|
||||
@start_user_id = start_user_id
|
||||
@end_user_id = end_user_id
|
||||
end
|
||||
|
||||
def execute
|
||||
User.where(id: start_user_id..end_user_id).select(:id).find_each do |user| # rubocop: disable CodeReuse/ActiveRecord
|
||||
Users::RefreshAuthorizedProjectsService.new(user).execute
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :start_user_id, :end_user_id
|
||||
end
|
||||
end
|
|
@ -77,7 +77,7 @@ module Ci
|
|||
|
||||
def update_processable!(processable)
|
||||
status = processable_status(processable)
|
||||
return unless HasStatus::COMPLETED_STATUSES.include?(status)
|
||||
return unless Ci::HasStatus::COMPLETED_STATUSES.include?(status)
|
||||
|
||||
# transition status if possible
|
||||
Gitlab::OptimisticLocking.retry_lock(processable) do |subject|
|
||||
|
|
|
@ -80,7 +80,7 @@ module Ci
|
|||
# TODO: This is hack to support
|
||||
# the same exact behaviour for Atomic and Legacy processing
|
||||
# that DAG is blocked from executing if dependent is not "complete"
|
||||
if dag && statuses.any? { |status| HasStatus::COMPLETED_STATUSES.exclude?(status[:status]) }
|
||||
if dag && statuses.any? { |status| Ci::HasStatus::COMPLETED_STATUSES.exclude?(status[:status]) }
|
||||
return 'pending'
|
||||
end
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ module Ci
|
|||
def process_stage_for_stage_scheduling(index)
|
||||
current_status = status_for_prior_stages(index)
|
||||
|
||||
return unless HasStatus::COMPLETED_STATUSES.include?(current_status)
|
||||
return unless Ci::HasStatus::COMPLETED_STATUSES.include?(current_status)
|
||||
|
||||
created_stage_scheduled_processables_in_stage(index).find_each.select do |build|
|
||||
process_build(build, current_status)
|
||||
|
@ -73,7 +73,7 @@ module Ci
|
|||
def process_dag_build_with_needs(build)
|
||||
current_status = status_for_build_needs(build.needs.map(&:name))
|
||||
|
||||
return unless HasStatus::COMPLETED_STATUSES.include?(current_status)
|
||||
return unless Ci::HasStatus::COMPLETED_STATUSES.include?(current_status)
|
||||
|
||||
process_build(build, current_status)
|
||||
end
|
||||
|
|
|
@ -93,25 +93,25 @@ module Projects
|
|||
old_repository_storage = project.repository_storage
|
||||
new_project_path = moved_path(project.disk_path)
|
||||
|
||||
# Notice that the block passed to `run_after_commit` will run with `project`
|
||||
# Notice that the block passed to `run_after_commit` will run with `repository_storage_move`
|
||||
# as its context
|
||||
project.run_after_commit do
|
||||
repository_storage_move.run_after_commit do
|
||||
GitlabShellWorker.perform_async(:mv_repository,
|
||||
old_repository_storage,
|
||||
disk_path,
|
||||
project.disk_path,
|
||||
new_project_path)
|
||||
|
||||
if wiki.repository_exists?
|
||||
if project.wiki.repository_exists?
|
||||
GitlabShellWorker.perform_async(:mv_repository,
|
||||
old_repository_storage,
|
||||
wiki.disk_path,
|
||||
project.wiki.disk_path,
|
||||
"#{new_project_path}.wiki")
|
||||
end
|
||||
|
||||
if design_repository.exists?
|
||||
if project.design_repository.exists?
|
||||
GitlabShellWorker.perform_async(:mv_repository,
|
||||
old_repository_storage,
|
||||
design_repository.disk_path,
|
||||
project.design_repository.disk_path,
|
||||
"#{new_project_path}.design")
|
||||
end
|
||||
end
|
||||
|
|
|
@ -85,8 +85,6 @@ module Users
|
|||
# remove - The IDs of the authorization rows to remove.
|
||||
# add - Rows to insert in the form `[user id, project id, access level]`
|
||||
def update_authorizations(remove = [], add = [])
|
||||
return if remove.empty? && add.empty?
|
||||
|
||||
User.transaction do
|
||||
user.remove_project_authorizations(remove) unless remove.empty?
|
||||
ProjectAuthorization.insert_authorizations(add) unless add.empty?
|
||||
|
|
|
@ -100,7 +100,7 @@
|
|||
.hint
|
||||
= parsed_with_gfm
|
||||
|
||||
.prepend-top-default.append-bottom-default
|
||||
.prepend-top-default.gl-mb-3
|
||||
= f.submit 'Update appearance settings', class: 'btn btn-success'
|
||||
- if @appearance.persisted? || @appearance.updated_at
|
||||
.mt-4
|
||||
|
|
|
@ -32,6 +32,15 @@
|
|||
= f.check_box :require_two_factor_authentication, class: 'form-check-input'
|
||||
= f.label :require_two_factor_authentication, class: 'form-check-label' do
|
||||
Require all users to set up Two-factor authentication
|
||||
.form-group
|
||||
= f.label :unknown_sign_in, _('Email notification for unknown sign-ins'), class: 'label-bold'
|
||||
.form-check
|
||||
= f.check_box :notify_on_unknown_sign_in, class: 'form-check-input'
|
||||
= f.label :notify_on_unknown_sign_in, class: 'form-check-label' do
|
||||
= _('Notify users by email when sign-in location is not recognized')
|
||||
= link_to icon('question-circle'),
|
||||
'https://docs.gitlab.com/ee/user/profile/unknown_sign_in_notification.html',
|
||||
target: '_blank'
|
||||
.form-group
|
||||
= f.label :two_factor_authentication, 'Two-factor grace period (hours)', class: 'label-bold'
|
||||
= f.number_field :two_factor_grace_period, min: 0, class: 'form-control', placeholder: '0'
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
- page_title _("Groups")
|
||||
|
||||
.top-area
|
||||
.prepend-top-default.append-bottom-default
|
||||
.prepend-top-default.gl-mb-3
|
||||
= form_tag admin_groups_path, method: :get, class: 'js-search-form' do |f|
|
||||
= hidden_field_tag :sort, @sort
|
||||
.search-holder
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
.row.prepend-top-default.append-bottom-default
|
||||
.row.prepend-top-default.gl-mb-3
|
||||
.col-lg-3
|
||||
%h4.gl-mt-0
|
||||
Recent Deliveries
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
.col-lg-3
|
||||
= render 'shared/web_hooks/title_and_docs', hook: @hook
|
||||
|
||||
.col-lg-9.append-bottom-default
|
||||
.col-lg-9.gl-mb-3
|
||||
= form_for @hook, as: :hook, url: admin_hook_path do |f|
|
||||
= render partial: 'form', locals: { form: f, hook: @hook }
|
||||
.form-actions
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
.col-lg-4
|
||||
= render 'shared/web_hooks/title_and_docs', hook: @hook
|
||||
|
||||
.col-lg-8.append-bottom-default
|
||||
.col-lg-8.gl-mb-3
|
||||
= form_for @hook, as: :hook, url: admin_hooks_path do |f|
|
||||
= render partial: 'form', locals: { form: f, hook: @hook }
|
||||
= f.submit _('Add system hook'), class: 'btn btn-success'
|
||||
|
|
|
@ -28,4 +28,4 @@
|
|||
= link_to "Identities", admin_user_identities_path(@user)
|
||||
= nav_link(controller: :impersonation_tokens) do
|
||||
= link_to "Impersonation Tokens", admin_user_impersonation_tokens_path(@user)
|
||||
.append-bottom-default
|
||||
.gl-mb-3
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
- link = link_to(s_('ClusterIntegration|sign up'), 'https://console.cloud.google.com/freetrial?utm_campaign=2018_cpanel&utm_source=gitlab&utm_medium=referral', target: '_blank', rel: 'noopener noreferrer')
|
||||
.bs-callout.gcp-signup-offer.alert.alert-block.alert-dismissable.prepend-top-default.append-bottom-default{ role: 'alert', data: { feature_id: UserCalloutsHelper::GCP_SIGNUP_OFFER, dismiss_endpoint: user_callouts_path } }
|
||||
.bs-callout.gcp-signup-offer.alert.alert-block.alert-dismissable.prepend-top-default.gl-mb-3{ role: 'alert', data: { feature_id: UserCalloutsHelper::GCP_SIGNUP_OFFER, dismiss_endpoint: user_callouts_path } }
|
||||
%button.close.js-close{ type: "button" } ×
|
||||
.gcp-signup-offer--content
|
||||
.gcp-signup-offer--icon.gl-mr-3
|
||||
|
|
|
@ -49,7 +49,7 @@
|
|||
- else
|
||||
.settings-message.text-center
|
||||
= _("You don't have any applications")
|
||||
.oauth-authorized-applications.prepend-top-20.append-bottom-default
|
||||
.oauth-authorized-applications.prepend-top-20.gl-mb-3
|
||||
- if user_oauth_applications?
|
||||
%h5
|
||||
= _("Authorized applications (%{size})") % { size: @authorized_apps.size + @authorized_anonymous_tokens.size }
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
%p= s_('Check the %{docs_link_start}documentation%{docs_link_end}.').html_safe % { docs_link_start: docs_link_start, docs_link_end: '</a>'.html_safe }
|
||||
|
||||
.form-group.append-bottom-default
|
||||
.form-group.gl-mb-3
|
||||
.form-check
|
||||
= f.check_box :lfs_enabled, checked: @group.lfs_enabled?, class: 'form-check-input', data: { qa_selector: 'lfs_checkbox' }
|
||||
= f.label :lfs_enabled, class: 'form-check-label' do
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
.form-group
|
||||
= render 'shared/allow_request_access', form: f
|
||||
|
||||
.form-group.append-bottom-default
|
||||
.form-group.gl-mb-3
|
||||
.form-check
|
||||
= f.check_box :share_with_group_lock, disabled: !can_change_share_with_group_lock?(@group), class: 'form-check-input'
|
||||
= f.label :share_with_group_lock, class: 'form-check-label' do
|
||||
|
@ -16,14 +16,14 @@
|
|||
= s_('GroupSettings|Prevent sharing a project within %{group} with other groups').html_safe % { group: group_link }
|
||||
%span.js-descr.text-muted= share_with_group_lock_help_text(@group)
|
||||
|
||||
.form-group.append-bottom-default
|
||||
.form-group.gl-mb-3
|
||||
.form-check
|
||||
= f.check_box :emails_disabled, checked: @group.emails_disabled?, disabled: !can_disable_group_emails?(@group), class: 'form-check-input'
|
||||
= f.label :emails_disabled, class: 'form-check-label' do
|
||||
%span.d-block= s_('GroupSettings|Disable email notifications')
|
||||
%span.text-muted= s_('GroupSettings|This setting will override user notification preferences for all members of the group, subgroups, and projects.')
|
||||
|
||||
.form-group.append-bottom-default
|
||||
.form-group.gl-mb-3
|
||||
.form-check
|
||||
= f.check_box :mentions_disabled, checked: @group.mentions_disabled?, class: 'form-check-input'
|
||||
= f.label :mentions_disabled, class: 'form-check-label' do
|
||||
|
|
|
@ -72,4 +72,4 @@
|
|||
- else
|
||||
%p
|
||||
= s_("Profiles|You don't have access to delete this user.")
|
||||
.append-bottom-default
|
||||
.gl-mb-3
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
%p
|
||||
= _('This is a list of devices that have logged into your account. Revoke any sessions that you do not recognize.')
|
||||
.col-lg-8
|
||||
.append-bottom-default
|
||||
.gl-mb-3
|
||||
|
||||
.card.border-0
|
||||
%ul.list-group.list-group-flush
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
%hr
|
||||
%h4.gl-mt-0
|
||||
= _('Linked emails (%{email_count})') % { email_count: @emails.load.size + 1 }
|
||||
.account-well.append-bottom-default
|
||||
.account-well.gl-mb-3
|
||||
%ul
|
||||
%li
|
||||
= _('Your Primary Email will be used for avatar detection.')
|
||||
|
|
|
@ -17,5 +17,5 @@
|
|||
%hr
|
||||
%h5
|
||||
= _('Your GPG keys (%{count})') % { count:@gpg_keys.count}
|
||||
.append-bottom-default
|
||||
.gl-mb-3
|
||||
= render 'key_table'
|
||||
|
|
|
@ -20,5 +20,5 @@
|
|||
%hr
|
||||
%h5
|
||||
= _('Your SSH keys (%{count})') % { count:@keys.count }
|
||||
.append-bottom-default
|
||||
.gl-mb-3
|
||||
= render 'key_table'
|
||||
|
|
|
@ -47,7 +47,7 @@
|
|||
= _('Projects (%{count})') % { count: @project_notifications.size }
|
||||
%p.account-well
|
||||
= _('To specify the notification level per project of a group you belong to, you need to visit project page and change notification level there.')
|
||||
.append-bottom-default
|
||||
.gl-mb-3
|
||||
%ul.bordered-list
|
||||
- @project_notifications.each do |setting|
|
||||
= render 'project_settings', setting: setting, project: setting.source
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
.form-group
|
||||
= f.label :password_confirmation, _('Password confirmation'), class: 'label-bold'
|
||||
= f.password_field :password_confirmation, required: true, class: 'form-control', data: { qa_selector: 'confirm_password_field' }
|
||||
.prepend-top-default.append-bottom-default
|
||||
.prepend-top-default.gl-mb-3
|
||||
= f.submit _('Save password'), class: "btn btn-success append-right-10", data: { qa_selector: 'save_password_button' }
|
||||
- unless @user.password_automatically_set?
|
||||
= link_to _('I forgot my password'), reset_profile_password_path, method: :put
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
.md
|
||||
= brand_profile_image_guidelines
|
||||
.col-lg-8
|
||||
.clearfix.avatar-image.append-bottom-default
|
||||
.clearfix.avatar-image.gl-mb-3
|
||||
= link_to avatar_icon_for_user(@user, 400), target: '_blank', rel: 'noopener noreferrer' do
|
||||
= image_tag avatar_icon_for_user(@user, 160), alt: '', class: 'avatar s160'
|
||||
%h5.gl-mt-0= s_("Profiles|Upload new avatar")
|
||||
|
@ -118,7 +118,7 @@
|
|||
= f.check_box :include_private_contributions, label: s_('Profiles|Include private contributions on my profile'), wrapper_class: 'mb-2', inline: true
|
||||
.help-block
|
||||
= s_("Profiles|Choose to show contributions of private projects on your public profile without any project, repository or organization information")
|
||||
.prepend-top-default.append-bottom-default
|
||||
.prepend-top-default.gl-mb-3
|
||||
= f.submit s_("Profiles|Update profile settings"), class: 'btn btn-success'
|
||||
= link_to _("Cancel"), user_path(current_user), class: 'btn btn-cancel'
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
= render 'shared/commit_well', commit: commit, ref: ref, project: project
|
||||
|
||||
- if is_project_overview
|
||||
.project-buttons.append-bottom-default{ class: ("js-show-on-project-root" if vue_file_list_enabled?) }
|
||||
.project-buttons.gl-mb-3{ class: ("js-show-on-project-root" if vue_file_list_enabled?) }
|
||||
= render 'stat_anchor_list', anchors: @project.statistics_buttons(show_auto_devops_callout: show_auto_devops_callout)
|
||||
|
||||
- if vue_file_list_enabled?
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
- if @wiki_home.present?
|
||||
%div{ class: container_class }
|
||||
.md.prepend-top-default.append-bottom-default
|
||||
.md.prepend-top-default.gl-mb-3
|
||||
= render_wiki_content(@wiki_home)
|
||||
- else
|
||||
- can_create_wiki = can?(current_user, :create_wiki, @project)
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
- file_name = params[:id].split("/").last ||= ""
|
||||
- is_markdown = Gitlab::MarkupHelper.gitlab_markdown?(file_name)
|
||||
|
||||
.file-holder-bottom-radius.file-holder.file.append-bottom-default
|
||||
.file-holder-bottom-radius.file-holder.file.gl-mb-3
|
||||
.js-file-title.file-title.align-items-center.clearfix{ data: { current_action: action } }
|
||||
.editor-ref.block-truncated
|
||||
= sprite_icon('fork', size: 12)
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
.text-center.prepend-top-default.append-bottom-default
|
||||
.text-center.prepend-top-default.gl-mb-3
|
||||
= icon('spinner spin 2x', 'aria-hidden' => 'true', 'aria-label' => 'Loading content…', class: 'qa-spinner')
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
.file-content#js-sketch-viewer{ data: { endpoint: blob_raw_path } }
|
||||
.js-loading-icon.text-center.prepend-top-default.append-bottom-default.js-loading-icon{ 'aria-label' => 'Loading Sketch preview' }
|
||||
.js-loading-icon.text-center.prepend-top-default.gl-mb-3.js-loading-icon{ 'aria-label' => 'Loading Sketch preview' }
|
||||
= icon('spinner spin 2x', 'aria-hidden' => 'true');
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
.file-content.is-stl-loading
|
||||
.text-center#js-stl-viewer{ data: { endpoint: blob_raw_path } }
|
||||
= icon('spinner spin 2x', class: 'prepend-top-default append-bottom-default', 'aria-hidden' => 'true', 'aria-label' => 'Loading')
|
||||
.text-center.prepend-top-default.append-bottom-default.stl-controls
|
||||
= icon('spinner spin 2x', class: 'prepend-top-default gl-mb-3', 'aria-hidden' => 'true', 'aria-label' => 'Loading')
|
||||
.text-center.prepend-top-default.gl-mb-3.stl-controls
|
||||
.btn-group
|
||||
%button.btn.btn-default.btn-sm.js-material-changer{ data: { type: 'wireframe' } }
|
||||
Wireframe
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
.row.prepend-top-default.append-bottom-default
|
||||
.row.prepend-top-default.gl-mb-3
|
||||
.col-lg-3
|
||||
%h4.gl-mt-0
|
||||
= _("Environments")
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
- can_create_project = current_user.can?(:create_projects, namespace)
|
||||
|
||||
- if forked_project = namespace.find_fork_of(@project)
|
||||
.bordered-box.fork-thumbnail.text-center.prepend-left-default.append-right-default.prepend-top-default.append-bottom-default.forked
|
||||
.bordered-box.fork-thumbnail.text-center.prepend-left-default.append-right-default.prepend-top-default.gl-mb-3.forked
|
||||
= link_to project_path(forked_project) do
|
||||
- if /no_((\w*)_)*avatar/.match(avatar)
|
||||
= group_icon(namespace, class: "avatar rect-avatar s100 identicon mx-auto")
|
||||
|
@ -12,7 +12,7 @@
|
|||
%h5.prepend-top-default
|
||||
= namespace.human_name
|
||||
- else
|
||||
.bordered-box.fork-thumbnail.text-center.prepend-left-default.append-right-default.prepend-top-default.append-bottom-default{ class: ("disabled" unless can_create_project) }
|
||||
.bordered-box.fork-thumbnail.text-center.prepend-left-default.append-right-default.prepend-top-default.gl-mb-3{ class: ("disabled" unless can_create_project) }
|
||||
= link_to project_forks_path(@project, namespace_key: namespace.id),
|
||||
method: "POST",
|
||||
class: ("disabled has-tooltip" unless can_create_project),
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
.row.gl-mt-7.append-bottom-default
|
||||
.row.gl-mt-7.gl-mb-3
|
||||
.col-lg-3
|
||||
%h4.gl-mt-0
|
||||
Recent Deliveries
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
- add_to_breadcrumbs _('Webhook Settings'), namespace_project_hooks_path
|
||||
- page_title _('Webhook Logs')
|
||||
|
||||
.row.prepend-top-default.append-bottom-default
|
||||
.row.prepend-top-default.gl-mb-3
|
||||
.col-lg-3
|
||||
%h4.gl-mt-0
|
||||
Request details
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
.col-lg-3
|
||||
= render 'shared/web_hooks/title_and_docs', hook: @hook
|
||||
|
||||
.col-lg-9.append-bottom-default
|
||||
.col-lg-9.gl-mb-3
|
||||
= form_for [@project.namespace.becomes(Namespace), @project, @hook], as: :hook, url: project_hook_path(@project, @hook) do |f|
|
||||
= render partial: 'shared/web_hooks/form', locals: { form: f, hook: @hook }
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
.col-lg-4
|
||||
= render 'shared/web_hooks/title_and_docs', hook: @hook
|
||||
|
||||
.col-lg-8.append-bottom-default
|
||||
.col-lg-8.gl-mb-3
|
||||
= form_for @hook, as: :hook, url: polymorphic_path([@project.namespace.becomes(Namespace), @project, :hooks]) do |f|
|
||||
= render partial: 'shared/web_hooks/form', locals: { form: f, hook: @hook }
|
||||
= f.submit 'Add webhook', class: 'btn btn-success'
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
The subject will be used as the title of the new issue, and the message will be the description.
|
||||
|
||||
= link_to 'Quick actions', help_page_path('user/project/quick_actions'), target: '_blank', tabindex: -1
|
||||
= link_to 'Quick actions', help_page_path('user/project/quick_actions'), target: '_blank'
|
||||
and styling with
|
||||
= link_to 'Markdown', help_page_path('user/markdown'), target: '_blank', tabindex: -1
|
||||
= link_to 'Markdown', help_page_path('user/markdown'), target: '_blank'
|
||||
are supported.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
.account-well.prepend-top-default.append-bottom-default
|
||||
.account-well.prepend-top-default.gl-mb-3
|
||||
%ul
|
||||
%li
|
||||
= _('The repository must be accessible over <code>http://</code>,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
- grouped_statuses = @stage.statuses.latest_ordered.group_by(&:status)
|
||||
- HasStatus::ORDERED_STATUSES.each do |ordered_status|
|
||||
- Ci::HasStatus::ORDERED_STATUSES.each do |ordered_status|
|
||||
- grouped_statuses.fetch(ordered_status, []).each do |status|
|
||||
%li
|
||||
= render 'ci/status/dropdown_graph_badge', subject: status
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
- page_title @protected_ref.name, _("Protected Branches")
|
||||
|
||||
.row.prepend-top-default.append-bottom-default
|
||||
.row.prepend-top-default.gl-mb-3
|
||||
.col-lg-3
|
||||
%h4.gl-mt-0.ref-name
|
||||
= @protected_ref.name
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
- page_title @protected_ref.name, _("Protected Tags")
|
||||
|
||||
.row.prepend-top-default.append-bottom-default
|
||||
.row.prepend-top-default.gl-mb-3
|
||||
.col-lg-3
|
||||
%h4.gl-mt-0.ref-name
|
||||
= @protected_ref.name
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
.row.prepend-top-default.append-bottom-default
|
||||
.row.prepend-top-default.gl-mb-3
|
||||
.col-lg-4
|
||||
%h4.gl-mt-0
|
||||
= @service.title
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
- if @project
|
||||
= render 'projects/services/prometheus/configuration_banner', project: @project, service: @service
|
||||
|
||||
%h4.append-bottom-default
|
||||
%h4.gl-mb-3
|
||||
= s_('PrometheusService|Manual configuration')
|
||||
%p
|
||||
= s_('PrometheusService|Select the Active checkbox to override the Auto Configuration with custom settings. If unchecked, Auto Configuration settings are used.')
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
%h4.gl-mt-0
|
||||
= s_('PrometheusService|Metrics')
|
||||
|
||||
.row.append-bottom-default.prometheus-metrics-monitoring.js-prometheus-metrics-monitoring
|
||||
.row.gl-mb-3.prometheus-metrics-monitoring.js-prometheus-metrics-monitoring
|
||||
= render 'projects/services/prometheus/metrics', project: @project
|
||||
|
||||
= render 'projects/services/prometheus/external_alerts', project: @project
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
- if @project
|
||||
= render 'projects/settings/operations/configuration_banner', project: @project, service: service
|
||||
|
||||
%b.append-bottom-default
|
||||
%b.gl-mb-3
|
||||
= s_('PrometheusService|Manual configuration')
|
||||
%p
|
||||
= s_('PrometheusService|Select the Active checkbox to override the Auto Configuration with custom settings. If unchecked, Auto Configuration settings are used.')
|
||||
|
|
|
@ -59,7 +59,7 @@
|
|||
%pre.wrap{ data: { qa_selector: 'tag_message_content' } }
|
||||
= strip_signature(@tag.message)
|
||||
|
||||
.append-bottom-default.prepend-top-default
|
||||
.gl-mb-3.prepend-top-default
|
||||
- if @release.description.present?
|
||||
.description.md{ data: { qa_selector: 'tag_release_notes_content' } }
|
||||
= markdown_field(@release, :description)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
.row.prepend-top-default.append-bottom-default.triggers-container
|
||||
.row.prepend-top-default.gl-mb-3.triggers-container
|
||||
.col-lg-12
|
||||
.card
|
||||
.card-header
|
||||
|
@ -21,7 +21,7 @@
|
|||
%th
|
||||
= render partial: 'projects/triggers/trigger', collection: @triggers, as: :trigger
|
||||
- else
|
||||
%p.settings-message.text-center.append-bottom-default
|
||||
%p.settings-message.text-center.gl-mb-3
|
||||
No triggers have been created yet. Add one using the form above.
|
||||
|
||||
.card-footer
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
- page_title _("Trigger")
|
||||
|
||||
.row.prepend-top-default.append-bottom-default
|
||||
.row.prepend-top-default.gl-mb-3
|
||||
.col-lg-12
|
||||
%h4.gl-mt-0
|
||||
Update trigger
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
.info-well.d-none.d-sm-block.project-last-commit.append-bottom-default
|
||||
.info-well.d-none.d-sm-block.project-last-commit.gl-mb-3
|
||||
.well-segment
|
||||
%ul.blob-commit-info
|
||||
= render 'projects/commits/commit', commit: commit, ref: ref, project: project
|
||||
|
|
|
@ -11,10 +11,10 @@
|
|||
.md-header
|
||||
%ul.nav.nav-tabs.nav-links.clearfix
|
||||
%li.md-header-tab.active
|
||||
%button.js-md-write-button{ tabindex: -1 }
|
||||
%button.js-md-write-button
|
||||
= _("Write")
|
||||
%li.md-header-tab
|
||||
%button.js-md-preview-button{ tabindex: -1 }
|
||||
%button.js-md-preview-button
|
||||
= _("Preview")
|
||||
|
||||
%li.md-header-toolbar.active
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue