diff options
151 files changed, 2110 insertions, 2003 deletions
diff --git a/.gitlab/ci/qa.gitlab-ci.yml b/.gitlab/ci/qa.gitlab-ci.yml index ac2a70dda0b..caa7d18c1ee 100644 --- a/.gitlab/ci/qa.gitlab-ci.yml +++ b/.gitlab/ci/qa.gitlab-ci.yml @@ -1,7 +1,6 @@ .package-and-qa-base: image: ruby:2.6-alpine stage: qa - needs: ["build-qa-image", "gitlab:assets:compile pull-cache"] dependencies: [] variables: GIT_DEPTH: "1" @@ -20,14 +19,24 @@ package-and-qa-manual: extends: - .package-and-qa-base - .except-docs-qa - when: manual except: - - master - - /(^docs[\/-].+|.+-docs$)/ - - /(^qa[\/-].*|.*-qa$) + refs: + - master + needs: ["build-qa-image", "gitlab:assets:compile pull-cache"] + when: manual + +package-and-qa-manual:master: + extends: + - .package-and-qa-base + needs: ["build-qa-image", "gitlab:assets:compile"] + only: + refs: + - master + when: manual package-and-qa: extends: .package-and-qa-base + needs: ["build-qa-image", "gitlab:assets:compile pull-cache"] allow_failure: true only: - /(^qa[\/-].*|.*-qa$)/@gitlab-org/gitlab-ce diff --git a/.gitlab/ci/review.gitlab-ci.yml b/.gitlab/ci/review.gitlab-ci.yml index 3415f1b6ab4..360e4433e57 100644 --- a/.gitlab/ci/review.gitlab-ci.yml +++ b/.gitlab/ci/review.gitlab-ci.yml @@ -22,7 +22,9 @@ - source scripts/utils.sh .review-docker: - extends: .review-base + extends: + - .default-tags + - .default-retry image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-qa-alpine services: - docker:19.03.0-dind @@ -36,7 +38,13 @@ QA_IMAGE: "${CI_REGISTRY}/${CI_PROJECT_PATH}/gitlab/${CI_PROJECT_NAME}-qa:${CI_COMMIT_REF_SLUG}" build-qa-image: - extends: .review-docker + extends: + - .review-docker + - .except-docs-qa + only: + refs: + - branches@gitlab-org/gitlab-ce + - branches@gitlab-org/gitlab-ee stage: test script: - time docker build --cache-from ${LATEST_QA_IMAGE} --tag ${QA_IMAGE} --file ./qa/Dockerfile ./ @@ -124,7 +132,9 @@ review-stop: artifacts: {} .review-qa-base: - extends: .review-docker + extends: + - .review-docker + - .review-only retry: 2 stage: qa variables: @@ -171,7 +171,7 @@ gem 'acts-as-taggable-on', '~> 6.0' gem 'sidekiq', '~> 5.2.7' gem 'sidekiq-cron', '~> 1.0' gem 'redis-namespace', '~> 1.6.0' -gem 'gitlab-sidekiq-fetcher', '0.5.1', require: 'sidekiq-reliable-fetch' +gem 'gitlab-sidekiq-fetcher', '0.5.2', require: 'sidekiq-reliable-fetch' # Cron Parser gem 'fugit', '~> 1.2.1' diff --git a/Gemfile.lock b/Gemfile.lock index c83f9b9076a..f0b3d722326 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -321,7 +321,7 @@ GEM gitlab-markup (1.7.0) gitlab-peek (0.0.1) railties (>= 4.0.0) - gitlab-sidekiq-fetcher (0.5.1) + gitlab-sidekiq-fetcher (0.5.2) sidekiq (~> 5) gitlab-styles (2.8.0) rubocop (~> 0.69.0) @@ -1098,7 +1098,7 @@ DEPENDENCIES gitlab-labkit (~> 0.5) gitlab-markup (~> 1.7.0) gitlab-peek (~> 0.0.1) - gitlab-sidekiq-fetcher (= 0.5.1) + gitlab-sidekiq-fetcher (= 0.5.2) gitlab-styles (~> 2.7) gitlab_omniauth-ldap (~> 2.1.1) gon (~> 6.2) diff --git a/app/assets/javascripts/flash.js b/app/assets/javascripts/flash.js index c2397842125..660f0f0ba3e 100644 --- a/app/assets/javascripts/flash.js +++ b/app/assets/javascripts/flash.js @@ -1,4 +1,5 @@ import _ from 'underscore'; +import { spriteIcon } from './lib/utils/common_utils'; const hideFlash = (flashEl, fadeTransition = true) => { if (fadeTransition) { @@ -35,16 +36,11 @@ const createAction = config => ` </a> `; -const createFlashEl = (message, type, isFixedLayout = false) => ` - <div - class="flash-${type}" - > - <div - class="flash-text ${ - isFixedLayout ? 'container-fluid container-limited limit-container-width' : '' - }" - > +const createFlashEl = (message, type) => ` + <div class="flash-content flash-${type} rounded"> + <div class="flash-text"> ${_.escape(message)} + ${spriteIcon('close', 'close-icon')} </div> </div> `; @@ -76,15 +72,10 @@ const createFlash = function createFlash( addBodyClass = false, ) { const flashContainer = parent.querySelector('.flash-container'); - const navigation = parent.querySelector('.content'); if (!flashContainer) return null; - const isFixedLayout = navigation - ? navigation.parentNode.classList.contains('container-limited') - : true; - - flashContainer.innerHTML = createFlashEl(message, type, isFixedLayout); + flashContainer.innerHTML = createFlashEl(message, type); const flashEl = flashContainer.querySelector(`.flash-${type}`); removeFlashClickListener(flashEl, fadeTransition); diff --git a/app/assets/javascripts/monitoring/components/charts/area.vue b/app/assets/javascripts/monitoring/components/charts/area.vue deleted file mode 100644 index cac10474d06..00000000000 --- a/app/assets/javascripts/monitoring/components/charts/area.vue +++ /dev/null @@ -1,304 +0,0 @@ -<script> -import { __ } from '~/locale'; -import { GlLink } from '@gitlab/ui'; -import { GlAreaChart, GlChartSeriesLabel } from '@gitlab/ui/dist/charts'; -import dateFormat from 'dateformat'; -import { debounceByAnimationFrame, roundOffFloat } from '~/lib/utils/common_utils'; -import { getSvgIconPathContent } from '~/lib/utils/icon_utils'; -import Icon from '~/vue_shared/components/icon.vue'; -import { chartHeight, graphTypes, lineTypes } from '../../constants'; -import { makeDataSeries } from '~/helpers/monitor_helper'; -import { graphDataValidatorForValues } from '../../utils'; - -let debouncedResize; - -// TODO: Remove this component in favor of the more general time_series.vue -// Please port all changes here to time_series.vue as well. - -export default { - components: { - GlAreaChart, - GlChartSeriesLabel, - GlLink, - Icon, - }, - inheritAttrs: false, - props: { - graphData: { - type: Object, - required: true, - validator: graphDataValidatorForValues.bind(null, false), - }, - containerWidth: { - type: Number, - required: true, - }, - deploymentData: { - type: Array, - required: false, - default: () => [], - }, - projectPath: { - type: String, - required: false, - default: () => '', - }, - showBorder: { - type: Boolean, - required: false, - default: () => false, - }, - singleEmbed: { - type: Boolean, - required: false, - default: false, - }, - thresholds: { - type: Array, - required: false, - default: () => [], - }, - }, - data() { - return { - tooltip: { - title: '', - content: [], - commitUrl: '', - isDeployment: false, - sha: '', - }, - width: 0, - height: chartHeight, - svgs: {}, - primaryColor: null, - }; - }, - computed: { - chartData() { - // Transforms & supplements query data to render appropriate labels & styles - // Input: [{ queryAttributes1 }, { queryAttributes2 }] - // Output: [{ seriesAttributes1 }, { seriesAttributes2 }] - return this.graphData.queries.reduce((acc, query) => { - const { appearance } = query; - const lineType = - appearance && appearance.line && appearance.line.type - ? appearance.line.type - : lineTypes.default; - const lineWidth = - appearance && appearance.line && appearance.line.width - ? appearance.line.width - : undefined; - - const series = makeDataSeries(query.result, { - name: this.formatLegendLabel(query), - lineStyle: { - type: lineType, - width: lineWidth, - }, - areaStyle: { - opacity: - appearance && appearance.area && typeof appearance.area.opacity === 'number' - ? appearance.area.opacity - : undefined, - }, - }); - - return acc.concat(series); - }, []); - }, - chartOptions() { - return { - xAxis: { - name: __('Time'), - type: 'time', - axisLabel: { - formatter: date => dateFormat(date, 'h:MM TT'), - }, - axisPointer: { - snap: true, - }, - }, - yAxis: { - name: this.yAxisLabel, - axisLabel: { - formatter: num => roundOffFloat(num, 3).toString(), - }, - }, - series: this.scatterSeries, - dataZoom: [this.dataZoomConfig], - }; - }, - dataZoomConfig() { - const handleIcon = this.svgs['scroll-handle']; - - return handleIcon ? { handleIcon } : {}; - }, - earliestDatapoint() { - return this.chartData.reduce((acc, series) => { - const { data } = series; - const { length } = data; - if (!length) { - return acc; - } - - const [first] = data[0]; - const [last] = data[length - 1]; - const seriesEarliest = first < last ? first : last; - - return seriesEarliest < acc || acc === null ? seriesEarliest : acc; - }, null); - }, - isMultiSeries() { - return this.tooltip.content.length > 1; - }, - recentDeployments() { - return this.deploymentData.reduce((acc, deployment) => { - if (deployment.created_at >= this.earliestDatapoint) { - acc.push({ - id: deployment.id, - createdAt: deployment.created_at, - sha: deployment.sha, - commitUrl: `${this.projectPath}/commit/${deployment.sha}`, - tag: deployment.tag, - tagUrl: deployment.tag ? `${this.tagsPath}/${deployment.ref.name}` : null, - ref: deployment.ref.name, - showDeploymentFlag: false, - }); - } - - return acc; - }, []); - }, - scatterSeries() { - return { - type: graphTypes.deploymentData, - data: this.recentDeployments.map(deployment => [deployment.createdAt, 0]), - symbol: this.svgs.rocket, - symbolSize: 14, - itemStyle: { - color: this.primaryColor, - }, - }; - }, - yAxisLabel() { - return `${this.graphData.y_label}`; - }, - }, - watch: { - containerWidth: 'onResize', - }, - beforeDestroy() { - window.removeEventListener('resize', debouncedResize); - }, - created() { - debouncedResize = debounceByAnimationFrame(this.onResize); - window.addEventListener('resize', debouncedResize); - this.setSvg('rocket'); - this.setSvg('scroll-handle'); - }, - methods: { - formatLegendLabel(query) { - return `${query.label}`; - }, - formatTooltipText(params) { - this.tooltip.title = dateFormat(params.value, 'dd mmm yyyy, h:MMTT'); - this.tooltip.content = []; - params.seriesData.forEach(seriesData => { - this.tooltip.isDeployment = seriesData.componentSubType === graphTypes.deploymentData; - if (this.tooltip.isDeployment) { - const [deploy] = this.recentDeployments.filter( - deployment => deployment.createdAt === seriesData.value[0], - ); - this.tooltip.sha = deploy.sha.substring(0, 8); - this.tooltip.commitUrl = deploy.commitUrl; - } else { - const { seriesName, color } = seriesData; - // seriesData.value contains the chart's [x, y] value pair - // seriesData.value[1] is threfore the chart y value - const value = seriesData.value[1].toFixed(3); - - this.tooltip.content.push({ - name: seriesName, - value, - color, - }); - } - }); - }, - setSvg(name) { - getSvgIconPathContent(name) - .then(path => { - if (path) { - this.$set(this.svgs, name, `path://${path}`); - } - }) - .catch(() => {}); - }, - onChartUpdated(chart) { - [this.primaryColor] = chart.getOption().color; - }, - onResize() { - if (!this.$refs.areaChart) return; - const { width } = this.$refs.areaChart.$el.getBoundingClientRect(); - this.width = width; - }, - }, -}; -</script> - -<template> - <div - class="prometheus-graph col-12" - :class="[showBorder ? 'p-2' : 'p-0', { 'col-lg-6': !singleEmbed }]" - > - <div :class="{ 'prometheus-graph-embed w-100 p-3': showBorder }"> - <div class="prometheus-graph-header"> - <h5 ref="graphTitle" class="prometheus-graph-title">{{ graphData.title }}</h5> - <div ref="graphWidgets" class="prometheus-graph-widgets"><slot></slot></div> - </div> - <gl-area-chart - ref="areaChart" - v-bind="$attrs" - :data="chartData" - :option="chartOptions" - :format-tooltip-text="formatTooltipText" - :thresholds="thresholds" - :width="width" - :height="height" - @updated="onChartUpdated" - > - <template v-if="tooltip.isDeployment"> - <template slot="tooltipTitle"> - {{ __('Deployed') }} - </template> - <div slot="tooltipContent" class="d-flex align-items-center"> - <icon name="commit" class="mr-2" /> - <gl-link :href="tooltip.commitUrl">{{ tooltip.sha }}</gl-link> - </div> - </template> - <template v-else> - <template slot="tooltipTitle"> - <div class="text-nowrap"> - {{ tooltip.title }} - </div> - </template> - <template slot="tooltipContent"> - <div - v-for="(content, key) in tooltip.content" - :key="key" - class="d-flex justify-content-between" - > - <gl-chart-series-label :color="isMultiSeries ? content.color : ''"> - {{ content.name }} - </gl-chart-series-label> - <div class="prepend-left-32"> - {{ content.value }} - </div> - </div> - </template> - </template> - </gl-area-chart> - </div> - </div> -</template> diff --git a/app/assets/javascripts/pages/profiles/two_factor_auths/index.js b/app/assets/javascripts/pages/profiles/two_factor_auths/index.js index 820f0f7f12d..0d377eb9c68 100644 --- a/app/assets/javascripts/pages/profiles/two_factor_auths/index.js +++ b/app/assets/javascripts/pages/profiles/two_factor_auths/index.js @@ -5,9 +5,10 @@ import { parseBoolean } from '~/lib/utils/common_utils'; document.addEventListener('DOMContentLoaded', () => { const twoFactorNode = document.querySelector('.js-two-factor-auth'); const skippable = parseBoolean(twoFactorNode.dataset.twoFactorSkippable); + if (skippable) { const button = `<a class="btn btn-sm btn-warning float-right" data-method="patch" href="${twoFactorNode.dataset.two_factor_skip_url}">Configure it later</a>`; - const flashAlert = document.querySelector('.flash-alert .container-fluid'); + const flashAlert = document.querySelector('.flash-alert'); if (flashAlert) flashAlert.insertAdjacentHTML('beforeend', button); } diff --git a/app/assets/stylesheets/framework/flash.scss b/app/assets/stylesheets/framework/flash.scss index 96f6d02a68f..af05d069f97 100644 --- a/app/assets/stylesheets/framework/flash.scss +++ b/app/assets/stylesheets/framework/flash.scss @@ -1,3 +1,5 @@ +$notification-box-shadow-color: rgba(0, 0, 0, 0.25); + .flash-container { cursor: pointer; margin: 0; @@ -6,12 +8,32 @@ position: relative; z-index: 1; + &.sticky { + position: sticky; + position: -webkit-sticky; + top: $flash-container-top; + z-index: 200; + + .flash-content { + box-shadow: 0 2px 4px 0 $notification-box-shadow-color; + } + } + + .close-icon { + width: 16px; + height: 16px; + position: absolute; + right: $gl-padding; + top: $gl-padding; + } + .flash-notice, .flash-alert, .flash-success, .flash-warning { border-radius: $border-radius-default; color: $white-light; + padding-right: $gl-padding * 2; .container-fluid, .container-fluid.container-limited { @@ -97,3 +119,28 @@ } } } + +.gl-browser-ie .flash-container { + position: fixed; + max-width: $limited-layout-width; + left: 50%; + + .flash-alert { + position: relative; + left: -50%; + } +} + +.with-system-header .flash-container { + top: $flash-container-top + $system-header-height; +} + +.with-performance-bar { + .flash-container { + top: $flash-container-top + $performance-bar-height; + } + + &.with-system-header .flash-container { + top: $flash-container-top + $performance-bar-height + $system-header-height; + } +} diff --git a/app/assets/stylesheets/framework/layout.scss b/app/assets/stylesheets/framework/layout.scss index 97cb9d90ff0..7205324e86f 100644 --- a/app/assets/stylesheets/framework/layout.scss +++ b/app/assets/stylesheets/framework/layout.scss @@ -29,6 +29,15 @@ body { } } +.container-fluid { + &.limit-container-width { + .flash-container.sticky { + max-width: $limited-layout-width; + margin: 0 auto; + } + } +} + .content-wrapper { margin-top: $header-height; padding-bottom: 100px; diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 7a3fd2adfbb..15a779dde1d 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -377,6 +377,7 @@ $performance-bar-height: 35px; $system-header-height: 16px; $system-footer-height: $system-header-height; $flash-height: 52px; +$flash-container-top: 48px; $context-header-height: 60px; $breadcrumb-min-height: 48px; $home-panel-title-row-height: 64px; diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 64e372878e6..2b6f10ef79f 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -460,8 +460,8 @@ module Ci canceled? && auto_canceled_by_id? end - def cancel_running - retry_optimistic_lock(cancelable_statuses) do |cancelable| + def cancel_running(retries: nil) + retry_optimistic_lock(cancelable_statuses, retries) do |cancelable| cancelable.find_each do |job| yield(job) if block_given? job.cancel @@ -469,10 +469,10 @@ module Ci end end - def auto_cancel_running(pipeline) + def auto_cancel_running(pipeline, retries: nil) update(auto_canceled_by: pipeline) - cancel_running do |job| + cancel_running(retries: retries) do |job| job.auto_canceled_by = pipeline end end @@ -670,6 +670,7 @@ module Ci variables.append(key: 'CI_COMMIT_REF_PROTECTED', value: (!!protected_ref?).to_s) if merge_request_event? && merge_request + variables.append(key: 'CI_MERGE_REQUEST_EVENT_TYPE', value: merge_request_event_type.to_s) variables.append(key: 'CI_MERGE_REQUEST_SOURCE_BRANCH_SHA', value: source_sha.to_s) variables.append(key: 'CI_MERGE_REQUEST_TARGET_BRANCH_SHA', value: target_sha.to_s) variables.concat(merge_request.predefined_variables) @@ -772,10 +773,18 @@ module Ci triggered_by_merge_request? && target_sha.present? end + def merge_train_pipeline? + merge_request_pipeline? && merge_train_ref? + end + def merge_request_ref? MergeRequest.merge_request_ref?(ref) end + def merge_train_ref? + MergeRequest.merge_train_ref?(ref) + end + def matches_sha_or_source_sha?(sha) self.sha == sha || self.source_sha == sha end @@ -804,6 +813,20 @@ module Ci errors ? errors.full_messages.to_sentence : "" end + def merge_request_event_type + return unless merge_request_event? + + strong_memoize(:merge_request_event_type) do + if detached_merge_request_pipeline? + :detached + elsif merge_request_pipeline? + :merged_result + elsif merge_train_pipeline? + :merge_train + end + end + end + private def ci_yaml_from_repo diff --git a/app/models/member.rb b/app/models/member.rb index dbae1076670..6457fe9ef0c 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -399,7 +399,7 @@ class Member < ApplicationRecord end def post_update_hook - # override in sub class + system_hook_service.execute_hooks_for(self, :update) end def post_destroy_hook diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index bfd636fa62a..28e450f9b30 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -1142,6 +1142,10 @@ class MergeRequest < ApplicationRecord ref.start_with?("refs/#{Repository::REF_MERGE_REQUEST}/") end + def self.merge_train_ref?(ref) + %r{\Arefs/#{Repository::REF_MERGE_REQUEST}/\d+/train\z}.match?(ref) + end + def in_locked_state begin lock_mr diff --git a/app/models/milestone.rb b/app/models/milestone.rb index 2ad2838111e..101e963ea29 100644 --- a/app/models/milestone.rb +++ b/app/models/milestone.rb @@ -24,6 +24,12 @@ class Milestone < ApplicationRecord belongs_to :project belongs_to :group + # A one-to-one relationship is set up here as part of a MVC: https://gitlab.com/gitlab-org/gitlab-ce/issues/62402 + # However, on the long term, we will want a many-to-many relationship between Release and Milestone. + # The "has_one through" allows us today to set up this one-to-one relationship while setting up the architecture for the long-term (ie intermediate table). + has_one :milestone_release + has_one :release, through: :milestone_release + has_internal_id :iid, scope: :project, init: ->(s) { s&.project&.milestones&.maximum(:iid) } has_internal_id :iid, scope: :group, init: ->(s) { s&.group&.milestones&.maximum(:iid) } @@ -59,6 +65,7 @@ class Milestone < ApplicationRecord validate :milestone_type_check validate :start_date_should_be_less_than_due_date, if: proc { |m| m.start_date.present? && m.due_date.present? } validate :dates_within_4_digits + validates_associated :milestone_release, message: -> (_, obj) { obj[:value].errors.full_messages.join(",") } strip_attributes :title diff --git a/app/models/milestone_release.rb b/app/models/milestone_release.rb new file mode 100644 index 00000000000..c8743a8cad8 --- /dev/null +++ b/app/models/milestone_release.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class MilestoneRelease < ApplicationRecord + belongs_to :milestone + belongs_to :release + + validates :milestone_id, uniqueness: { scope: [:release_id] } + validate :same_project_between_milestone_and_release + + private + + def same_project_between_milestone_and_release + return if milestone&.project_id == release&.project_id + + errors.add(:base, 'does not have the same project as the milestone') + end +end diff --git a/app/models/release.rb b/app/models/release.rb index 459a7c29ad0..b2e65974aa0 100644 --- a/app/models/release.rb +++ b/app/models/release.rb @@ -12,6 +12,12 @@ class Release < ApplicationRecord has_many :links, class_name: 'Releases::Link' + # A one-to-one relationship is set up here as part of a MVC: https://gitlab.com/gitlab-org/gitlab-ce/issues/62402 + # However, on the long term, we will want a many-to-many relationship between Release and Milestone. + # The "has_one through" allows us today to set up this one-to-one relationship while setting up the architecture for the long-term (ie intermediate table). + has_one :milestone_release + has_one :milestone, through: :milestone_release + default_value_for :released_at, allows_nil: false do Time.zone.now end @@ -20,6 +26,7 @@ class Release < ApplicationRecord validates :description, :project, :tag, presence: true validates :name, presence: true, on: :create + validates_associated :milestone_release, message: -> (_, obj) { obj[:value].errors.full_messages.join(",") } scope :sorted, -> { order(released_at: :desc) } diff --git a/app/models/repository.rb b/app/models/repository.rb index 7882b2b3036..5cb4b56a114 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -456,6 +456,10 @@ class Repository def after_import expire_content_cache + # This call is stubbed in tests due to being an expensive operation + # It can be reenabled for specific tests via: + # + # allow(DetectRepositoryLanguagesWorker).to receive(:perform_async).and_call_original DetectRepositoryLanguagesWorker.perform_async(project.id) end diff --git a/app/presenters/ci/pipeline_presenter.rb b/app/presenters/ci/pipeline_presenter.rb index 358473d0a74..a96f97988b2 100644 --- a/app/presenters/ci/pipeline_presenter.rb +++ b/app/presenters/ci/pipeline_presenter.rb @@ -34,6 +34,18 @@ module Ci end end + NAMES = { + merge_train: s_('Pipeline|Merge train pipeline'), + merged_result: s_('Pipeline|Merged result pipeline'), + detached: s_('Pipeline|Detached merge request pipeline') + }.freeze + + def name + # Currently, `merge_request_event_type` is the only source to name pipelines + # but this could be extended with the other types in the future. + NAMES.fetch(pipeline.merge_request_event_type, s_('Pipeline|Pipeline')) + end + def ref_text if pipeline.detached_merge_request_pipeline? _("for %{link_to_merge_request} with %{link_to_merge_request_source_branch}").html_safe % { link_to_merge_request: link_to_merge_request, link_to_merge_request_source_branch: link_to_merge_request_source_branch } diff --git a/app/serializers/pipeline_entity.rb b/app/serializers/pipeline_entity.rb index 9ef93b2387f..94e8b174f0f 100644 --- a/app/serializers/pipeline_entity.rb +++ b/app/serializers/pipeline_entity.rb @@ -2,6 +2,9 @@ class PipelineEntity < Grape::Entity include RequestAwareEntity + include Gitlab::Utils::StrongMemoize + + delegate :name, :failure_reason, to: :presented_pipeline expose :id expose :user, using: UserEntity @@ -36,6 +39,7 @@ class PipelineEntity < Grape::Entity expose :ordered_stages, as: :stages, using: StageEntity expose :duration expose :finished_at + expose :name end expose :merge_request, if: -> (*) { has_presentable_merge_request? }, with: MergeRequestForPipelineEntity do |pipeline| @@ -59,13 +63,11 @@ class PipelineEntity < Grape::Entity end expose :commit, using: CommitEntity + expose :merge_request_event_type, if: -> (pipeline, _) { pipeline.merge_request_event? } expose :source_sha, if: -> (pipeline, _) { pipeline.merge_request_pipeline? } expose :target_sha, if: -> (pipeline, _) { pipeline.merge_request_pipeline? } expose :yaml_errors, if: -> (pipeline, _) { pipeline.has_yaml_errors? } - - expose :failure_reason, if: -> (pipeline, _) { pipeline.failure_reason? } do |pipeline| - pipeline.present.failure_reason - end + expose :failure_reason, if: -> (pipeline, _) { pipeline.failure_reason? } expose :retry_path, if: -> (*) { can_retry? } do |pipeline| retry_project_pipeline_path(pipeline.project, pipeline) @@ -97,4 +99,10 @@ class PipelineEntity < Grape::Entity def detailed_status pipeline.detailed_status(request.current_user) end + + def presented_pipeline + strong_memoize(:presented_pipeline) do + pipeline.present + end + end end diff --git a/app/services/auth/container_registry_authentication_service.rb b/app/services/auth/container_registry_authentication_service.rb index 0a069320936..9e7319c1d9b 100644 --- a/app/services/auth/container_registry_authentication_service.rb +++ b/app/services/auth/container_registry_authentication_service.rb @@ -124,13 +124,21 @@ module Auth build_can_pull?(requested_project) || user_can_pull?(requested_project) || deploy_token_can_pull?(requested_project) when 'push' build_can_push?(requested_project) || user_can_push?(requested_project) - when '*', 'delete' + when 'delete' + build_can_delete?(requested_project) || user_can_admin?(requested_project) + when '*' user_can_admin?(requested_project) else false end end + def build_can_delete?(requested_project) + # Build can delete only from the project from which it originates + has_authentication_ability?(:build_destroy_container_image) && + requested_project == project + end + def registry Gitlab.config.registry end diff --git a/app/services/releases/concerns.rb b/app/services/releases/concerns.rb index 618d96717b8..b5412e97284 100644 --- a/app/services/releases/concerns.rb +++ b/app/services/releases/concerns.rb @@ -47,6 +47,27 @@ module Releases project.repository end end + + def milestone + return unless params[:milestone] + + strong_memoize(:milestone) do + MilestonesFinder.new( + project: project, + current_user: current_user, + project_ids: Array(project.id), + title: params[:milestone] + ).execute.first + end + end + + def inexistent_milestone? + params[:milestone] && !params[:milestone].empty? && !milestone + end + + def param_for_milestone_title_provided? + params[:milestone].present? || params[:milestone]&.empty? + end end end end diff --git a/app/services/releases/create_service.rb b/app/services/releases/create_service.rb index 5b13ac631ba..c91d43084d3 100644 --- a/app/services/releases/create_service.rb +++ b/app/services/releases/create_service.rb @@ -7,6 +7,7 @@ module Releases def execute return error('Access Denied', 403) unless allowed? return error('Release already exists', 409) if release + return error('Milestone does not exist', 400) if inexistent_milestone? tag = ensure_tag @@ -59,7 +60,8 @@ module Releases tag: tag.name, sha: tag.dereferenced_target.sha, released_at: released_at, - links_attributes: params.dig(:assets, 'links') || [] + links_attributes: params.dig(:assets, 'links') || [], + milestone: milestone ) end end diff --git a/app/services/releases/update_service.rb b/app/services/releases/update_service.rb index fabfa398c59..70acc68f747 100644 --- a/app/services/releases/update_service.rb +++ b/app/services/releases/update_service.rb @@ -9,6 +9,9 @@ module Releases return error('Release does not exist', 404) unless release return error('Access Denied', 403) unless allowed? return error('params is empty', 400) if empty_params? + return error('Milestone does not exist', 400) if inexistent_milestone? + + params[:milestone] = milestone if param_for_milestone_title_provided? if release.update(params) success(tag: existing_tag, release: release) diff --git a/app/services/system_hooks_service.rb b/app/services/system_hooks_service.rb index 858e04f43b2..34260d12a62 100644 --- a/app/services/system_hooks_service.rb +++ b/app/services/system_hooks_service.rb @@ -74,9 +74,11 @@ class SystemHooksService when ProjectMember return "user_add_to_team" if event == :create return "user_remove_from_team" if event == :destroy + return "user_update_for_team" if event == :update when GroupMember return 'user_add_to_group' if event == :create return 'user_remove_from_group' if event == :destroy + return 'user_update_for_group' if event == :update else "#{model.class.name.downcase}_#{event}" end diff --git a/app/views/admin/background_jobs/show.html.haml b/app/views/admin/background_jobs/show.html.haml index a0a00ac5d96..1001a69b787 100644 --- a/app/views/admin/background_jobs/show.html.haml +++ b/app/views/admin/background_jobs/show.html.haml @@ -1,10 +1,8 @@ -- @no_container = true - page_title "Background Jobs" -%div{ class: container_class } - %h3.page-title Background Jobs - %p.light GitLab uses #{link_to "sidekiq", "http://sidekiq.org/"} library for async job processing +%h3.page-title Background Jobs +%p.light GitLab uses #{link_to "sidekiq", "http://sidekiq.org/"} library for async job processing - %hr - .card - %iframe{ src: sidekiq_path, width: '100%', height: 970, style: "border: 0" } +%hr +.card + %iframe{ src: sidekiq_path, width: '100%', height: 970, style: "border: 0" } diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml index c29ecb43fe6..8aca61efe7b 100644 --- a/app/views/admin/dashboard/index.html.haml +++ b/app/views/admin/dashboard/index.html.haml @@ -1,167 +1,165 @@ -- @no_container = true - breadcrumb_title "Dashboard" -%div{ class: container_class } - = render_if_exists 'admin/licenses/breakdown', license: @license += render_if_exists 'admin/licenses/breakdown', license: @license - .admin-dashboard.prepend-top-default - .row - .col-sm-4 - .info-well.dark-well - .well-segment.well-centered - = link_to admin_projects_path do - %h3.text-center - Projects: - = approximate_count_with_delimiters(@counts, Project) - %hr - = link_to('New project', new_project_path, class: "btn btn-success") - .col-sm-4 - .info-well.dark-well - .well-segment.well-centered - = link_to admin_users_path do - %h3.text-center - Users: - = approximate_count_with_delimiters(@counts, User) - %hr - .btn-group.d-flex{ role: 'group' } - = link_to 'New user', new_admin_user_path, class: "btn btn-success" - = render_if_exists 'admin/dashboard/users_statistics' - .col-sm-4 - .info-well.dark-well - .well-segment.well-centered - = link_to admin_groups_path do - %h3.text-center - Groups: - = approximate_count_with_delimiters(@counts, Group) - %hr - = link_to 'New group', new_admin_group_path, class: "btn btn-success" - .row - .col-md-4 - .info-well - .well-segment.admin-well.admin-well-statistics - %h4 Statistics - %p - Forks - %span.light.float-right - = approximate_fork_count_with_delimiters(@counts) - %p - Issues - %span.light.float-right - = approximate_count_with_delimiters(@counts, Issue) - %p - Merge Requests - %span.light.float-right - = approximate_count_with_delimiters(@counts, MergeRequest) - %p - Notes - %span.light.float-right - = approximate_count_with_delimiters(@counts, Note) - %p - Snippets - %span.light.float-right - = approximate_count_with_delimiters(@counts, Snippet) - %p - SSH Keys - %span.light.float-right - = approximate_count_with_delimiters(@counts, Key) - %p - Milestones - %span.light.float-right - = approximate_count_with_delimiters(@counts, Milestone) - %p - Active Users - %span.light.float-right - = number_with_delimiter(User.active.count) - .col-md-4 - .info-well - .well-segment.admin-well.admin-well-features - %h4 Features - = feature_entry(_('Sign up'), href: admin_application_settings_path(anchor: 'js-signup-settings')) - = feature_entry(_('LDAP'), enabled: Gitlab.config.ldap.enabled) - = feature_entry(_('Gravatar'), href: admin_application_settings_path(anchor: 'js-account-settings'), enabled: gravatar_enabled?) - = feature_entry(_('OmniAuth'), href: admin_application_settings_path(anchor: 'js-signin-settings'), enabled: Gitlab::Auth.omniauth_enabled?) - = feature_entry(_('Reply by email'), enabled: Gitlab::IncomingEmail.enabled?) +.admin-dashboard.prepend-top-default + .row + .col-sm-4 + .info-well.dark-well + .well-segment.well-centered + = link_to admin_projects_path do + %h3.text-center + Projects: + = approximate_count_with_delimiters(@counts, Project) + %hr + = link_to('New project', new_project_path, class: "btn btn-success") + .col-sm-4 + .info-well.dark-well + .well-segment.well-centered + = link_to admin_users_path do + %h3.text-center + Users: + = approximate_count_with_delimiters(@counts, User) + %hr + .btn-group.d-flex{ role: 'group' } + = link_to 'New user', new_admin_user_path, class: "btn btn-success" + = render_if_exists 'admin/dashboard/users_statistics' + .col-sm-4 + .info-well.dark-well + .well-segment.well-centered + = link_to admin_groups_path do + %h3.text-center + Groups: + = approximate_count_with_delimiters(@counts, Group) + %hr + = link_to 'New group', new_admin_group_path, class: "btn btn-success" + .row + .col-md-4 + .info-well + .well-segment.admin-well.admin-well-statistics + %h4 Statistics + %p + Forks + %span.light.float-right + = approximate_fork_count_with_delimiters(@counts) + %p + Issues + %span.light.float-right + = approximate_count_with_delimiters(@counts, Issue) + %p + Merge Requests + %span.light.float-right + = approximate_count_with_delimiters(@counts, MergeRequest) + %p + Notes + %span.light.float-right + = approximate_count_with_delimiters(@counts, Note) + %p + Snippets + %span.light.float-right + = approximate_count_with_delimiters(@counts, Snippet) + %p + SSH Keys + %span.light.float-right + = approximate_count_with_delimiters(@counts, Key) + %p + Milestones + %span.light.float-right + = approximate_count_with_delimiters(@counts, Milestone) + %p + Active Users + %span.light.float-right + = number_with_delimiter(User.active.count) + .col-md-4 + .info-well + .well-segment.admin-well.admin-well-features + %h4 Features + = feature_entry(_('Sign up'), href: admin_application_settings_path(anchor: 'js-signup-settings')) + = feature_entry(_('LDAP'), enabled: Gitlab.config.ldap.enabled) + = feature_entry(_('Gravatar'), href: admin_application_settings_path(anchor: 'js-account-settings'), enabled: gravatar_enabled?) + = feature_entry(_('OmniAuth'), href: admin_application_settings_path(anchor: 'js-signin-settings'), enabled: Gitlab::Auth.omniauth_enabled?) + = feature_entry(_('Reply by email'), enabled: Gitlab::IncomingEmail.enabled?) - = render_if_exists 'admin/dashboard/elastic_and_geo' + = render_if_exists 'admin/dashboard/elastic_and_geo' - = feature_entry(_('Container Registry'), href: ci_cd_admin_application_settings_path(anchor: 'js-registry-settings'), enabled: Gitlab.config.registry.enabled) - = feature_entry(_('Gitlab Pages'), href: help_instance_configuration_url, enabled: Gitlab.config.pages.enabled) - = feature_entry(_('Shared Runners'), href: admin_runners_path, enabled: Gitlab.config.gitlab_ci.shared_runners_enabled) - .col-md-4 - .info-well - .well-segment.admin-well - %h4 - Components - - if Gitlab::CurrentSettings.version_check_enabled - .float-right - = version_status_badge - %p - %a{ href: admin_application_settings_path } - GitLab + = feature_entry(_('Container Registry'), href: ci_cd_admin_application_settings_path(anchor: 'js-registry-settings'), enabled: Gitlab.config.registry.enabled) + = feature_entry(_('Gitlab Pages'), href: help_instance_configuration_url, enabled: Gitlab.config.pages.enabled) + = feature_entry(_('Shared Runners'), href: admin_runners_path, enabled: Gitlab.config.gitlab_ci.shared_runners_enabled) + .col-md-4 + .info-well + .well-segment.admin-well + %h4 + Components + - if Gitlab::CurrentSettings.version_check_enabled + .float-right + = version_status_badge + %p + %a{ href: admin_application_settings_path } + GitLab + %span.float-right + = Gitlab::VERSION + = "(#{Gitlab.revision})" + %p + GitLab Shell + %span.float-right + = Gitlab::Shell.new.version + %p + GitLab Workhorse + %span.float-right + = gitlab_workhorse_version + %p + GitLab API + %span.float-right + = API::API::version + - if Gitlab.config.pages.enabled + %p + GitLab Pages %span.float-right - = Gitlab::VERSION - = "(#{Gitlab.revision})" - %p - GitLab Shell - %span.float-right - = Gitlab::Shell.new.version - %p - GitLab Workhorse - %span.float-right - = gitlab_workhorse_version - %p - GitLab API - %span.float-right - = API::API::version - - if Gitlab.config.pages.enabled - %p - GitLab Pages - %span.float-right - = Gitlab::Pages::VERSION + = Gitlab::Pages::VERSION - = render_if_exists 'admin/dashboard/geo' + = render_if_exists 'admin/dashboard/geo' - %p - Ruby - %span.float-right - #{RUBY_VERSION}p#{RUBY_PATCHLEVEL} - %p - Rails - %span.float-right - #{Rails::VERSION::STRING} - %p - = Gitlab::Database.human_adapter_name - %span.float-right - = Gitlab::Database.version - %p - = link_to "Gitaly Servers", admin_gitaly_servers_path - .row - .col-md-4 - .info-well - .well-segment.admin-well - %h4 Latest projects - - @projects.each do |project| - %p - = link_to project.full_name, admin_project_path(project), class: 'str-truncated-60' - %span.light.float-right - #{time_ago_with_tooltip(project.created_at)} - .col-md-4 - .info-well - .well-segment.admin-well - %h4 Latest users - - @users.each do |user| - %p - = link_to [:admin, user], class: 'str-truncated-60' do - = user.name - %span.light.float-right - #{time_ago_with_tooltip(user.created_at)} - .col-md-4 - .info-well - .well-segment.admin-well - %h4 Latest groups - - @groups.each do |group| - %p - = link_to [:admin, group], class: 'str-truncated-60' do - = group.full_name - %span.light.float-right - #{time_ago_with_tooltip(group.created_at)} + %p + Ruby + %span.float-right + #{RUBY_VERSION}p#{RUBY_PATCHLEVEL} + %p + Rails + %span.float-right + #{Rails::VERSION::STRING} + %p + = Gitlab::Database.human_adapter_name + %span.float-right + = Gitlab::Database.version + %p + = link_to "Gitaly Servers", admin_gitaly_servers_path + .row + .col-md-4 + .info-well + .well-segment.admin-well + %h4 Latest projects + - @projects.each do |project| + %p + = link_to project.full_name, admin_project_path(project), class: 'str-truncated-60' + %span.light.float-right + #{time_ago_with_tooltip(project.created_at)} + .col-md-4 + .info-well + .well-segment.admin-well + %h4 Latest users + - @users.each do |user| + %p + = link_to [:admin, user], class: 'str-truncated-60' do + = user.name + %span.light.float-right + #{time_ago_with_tooltip(user.created_at)} + .col-md-4 + .info-well + .well-segment.admin-well + %h4 Latest groups + - @groups.each do |group| + %p + = link_to [:admin, group], class: 'str-truncated-60' do + = group.full_name + %span.light.float-right + #{time_ago_with_tooltip(group.created_at)} diff --git a/app/views/admin/groups/index.html.haml b/app/views/admin/groups/index.html.haml index cb833ffd9ac..434b6e3a37e 100644 --- a/app/views/admin/groups/index.html.haml +++ b/app/views/admin/groups/index.html.haml @@ -1,20 +1,18 @@ -- @no_container = true - page_title _("Groups") -%div{ class: container_class } - .top-area - .prepend-top-default.append-bottom-default - = form_tag admin_groups_path, method: :get, class: 'js-search-form' do |f| - = hidden_field_tag :sort, @sort - .search-holder - - project_name = params[:name].present? ? params[:name] : nil - .search-field-holder - = search_field_tag :name, project_name, class: "form-control search-text-input js-search-input", autofocus: true, spellcheck: false, placeholder: 'Search by name' - = icon("search", class: "search-icon") - = render "shared/groups/dropdown", options_hash: admin_groups_sort_options_hash - = link_to new_admin_group_path, class: "btn btn-success" do - = _('New group') - %ul.content-list - = render @groups +.top-area + .prepend-top-default.append-bottom-default + = form_tag admin_groups_path, method: :get, class: 'js-search-form' do |f| + = hidden_field_tag :sort, @sort + .search-holder + - project_name = params[:name].present? ? params[:name] : nil + .search-field-holder + = search_field_tag :name, project_name, class: "form-control search-text-input js-search-input", autofocus: true, spellcheck: false, placeholder: 'Search by name' + = icon("search", class: "search-icon") + = render "shared/groups/dropdown", options_hash: admin_groups_sort_options_hash + = link_to new_admin_group_path, class: "btn btn-success" do + = _('New group') +%ul.content-list + = render @groups - = paginate @groups, theme: "gitlab" += paginate @groups, theme: "gitlab" diff --git a/app/views/admin/health_check/show.html.haml b/app/views/admin/health_check/show.html.haml index ac56e354a4d..587bfba8d47 100644 --- a/app/views/admin/health_check/show.html.haml +++ b/app/views/admin/health_check/show.html.haml @@ -1,41 +1,39 @@ -- @no_container = true - page_title _('Health Check') - no_errors = @errors.blank? -%div{ class: container_class } - %h3.page-title= page_title - .bs-callout.clearfix - .float-left - %p - #{ s_('HealthCheck|Access token is') } - %code#health-check-token= Gitlab::CurrentSettings.health_check_access_token - .prepend-top-10 - = button_to _("Reset health check access token"), reset_health_check_token_admin_application_settings_path, - method: :put, class: 'btn btn-default', - data: { confirm: _('Are you sure you want to reset the health check token?') } - %p.light - #{ _('Health information can be retrieved from the following endpoints. More information is available') } - = link_to s_('More information is available|here'), help_page_path('user/admin_area/monitoring/health_check') - %ul - %li - %code= readiness_url(token: Gitlab::CurrentSettings.health_check_access_token) - %li - %code= liveness_url(token: Gitlab::CurrentSettings.health_check_access_token) - %li - %code= metrics_url(token: Gitlab::CurrentSettings.health_check_access_token) - = render_if_exists 'admin/health_check/health_check_url' - %hr - .card - .card-header - Current Status: - - if no_errors - = icon('circle', class: 'cgreen') - #{ s_('HealthCheck|Healthy') } - - else - = icon('warning', class: 'cred') - #{ s_('HealthCheck|Unhealthy') } - .card-body - - if no_errors - #{ s_('HealthCheck|No Health Problems Detected') } - - else - = @errors +%h3.page-title= page_title +.bs-callout.clearfix + .float-left + %p + #{ s_('HealthCheck|Access token is') } + %code#health-check-token= Gitlab::CurrentSettings.health_check_access_token + .prepend-top-10 + = button_to _("Reset health check access token"), reset_health_check_token_admin_application_settings_path, + method: :put, class: 'btn btn-default', + data: { confirm: _('Are you sure you want to reset the health check token?') } +%p.light + #{ _('Health information can be retrieved from the following endpoints. More information is available') } + = link_to s_('More information is available|here'), help_page_path('user/admin_area/monitoring/health_check') + %ul + %li + %code= readiness_url(token: Gitlab::CurrentSettings.health_check_access_token) + %li + %code= liveness_url(token: Gitlab::CurrentSettings.health_check_access_token) + %li + %code= metrics_url(token: Gitlab::CurrentSettings.health_check_access_token) + = render_if_exists 'admin/health_check/health_check_url' +%hr +.card + .card-header + Current Status: + - if no_errors + = icon('circle', class: 'cgreen') + #{ s_('HealthCheck|Healthy') } + - else + = icon('warning', class: 'cred') + #{ s_('HealthCheck|Unhealthy') } + .card-body + - if no_errors + #{ s_('HealthCheck|No Health Problems Detected') } + - else + = @errors diff --git a/app/views/admin/jobs/index.html.haml b/app/views/admin/jobs/index.html.haml index 4e3e2f7a475..f1bdd52b399 100644 --- a/app/views/admin/jobs/index.html.haml +++ b/app/views/admin/jobs/index.html.haml @@ -1,22 +1,19 @@ - breadcrumb_title "Jobs" -- @no_container = true -%div{ class: container_class } +.top-area.scrolling-tabs-container.inner-page-scroll-tabs + - build_path_proc = ->(scope) { admin_jobs_path(scope: scope) } + = render "shared/builds/tabs", build_path_proc: build_path_proc, all_builds: @all_builds, scope: @scope - .top-area.scrolling-tabs-container.inner-page-scroll-tabs - - build_path_proc = ->(scope) { admin_jobs_path(scope: scope) } - = render "shared/builds/tabs", build_path_proc: build_path_proc, all_builds: @all_builds, scope: @scope + - if @all_builds.running_or_pending.any? + #stop-jobs-modal + .nav-controls + %button#stop-jobs-button.btn.btn-danger{ data: { toggle: 'modal', + target: '#stop-jobs-modal', + url: cancel_all_admin_jobs_path } } + = s_('AdminArea|Stop all jobs') - - if @all_builds.running_or_pending.any? - #stop-jobs-modal - .nav-controls - %button#stop-jobs-button.btn.btn-danger{ data: { toggle: 'modal', - target: '#stop-jobs-modal', - url: cancel_all_admin_jobs_path } } - = s_('AdminArea|Stop all jobs') +.row-content-block.second-block + #{(@scope || 'all').capitalize} jobs - .row-content-block.second-block - #{(@scope || 'all').capitalize} jobs - - %ul.content-list.builds-content-list.admin-builds-table - = render "projects/jobs/table", builds: @builds, admin: true +%ul.content-list.builds-content-list.admin-builds-table + = render "projects/jobs/table", builds: @builds, admin: true diff --git a/app/views/admin/logs/show.html.haml b/app/views/admin/logs/show.html.haml index e4c0382a437..eb93f645ea6 100644 --- a/app/views/admin/logs/show.html.haml +++ b/app/views/admin/logs/show.html.haml @@ -1,26 +1,24 @@ -- @no_container = true - page_title "Logs" -%div{ class: container_class } - %ul.nav-links.log-tabs.nav.nav-tabs - - @loggers.each do |klass| - %li.nav-item - = link_to klass.file_name, "##{klass.file_name_noext}", data: { toggle: 'tab' }, class: "#{active_when(klass == @loggers.first)} nav-link" - .row-content-block - To prevent performance issues admin logs output the last 2000 lines - .tab-content - - @loggers.each do |klass| - .tab-pane{ class: active_when(klass == @loggers.first), id: klass.file_name_noext } - .file-holder#README - .js-file-title.file-title - %i.fa.fa-file - = klass.file_name - .float-right - = link_to '#', class: 'log-bottom' do - %i.fa.fa-arrow-down - Scroll down - .file-content.logs - %ol - - klass.read_latest.each do |line| - %li - %p= line +%ul.nav-links.log-tabs.nav.nav-tabs + - @loggers.each do |klass| + %li.nav-item + = link_to klass.file_name, "##{klass.file_name_noext}", data: { toggle: 'tab' }, class: "#{active_when(klass == @loggers.first)} nav-link" +.row-content-block + To prevent performance issues admin logs output the last 2000 lines +.tab-content + - @loggers.each do |klass| + .tab-pane{ class: active_when(klass == @loggers.first), id: klass.file_name_noext } + .file-holder#README + .js-file-title.file-title + %i.fa.fa-file + = klass.file_name + .float-right + = link_to '#', class: 'log-bottom' do + %i.fa.fa-arrow-down + Scroll down + .file-content.logs + %ol + - klass.read_latest.each do |line| + %li + %p= line diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml index b88b760536d..7e03eb4f075 100644 --- a/app/views/admin/projects/index.html.haml +++ b/app/views/admin/projects/index.html.haml @@ -1,44 +1,41 @@ -- @no_container = true - page_title "Projects" - params[:visibility_level] ||= [] +.top-area.scrolling-tabs-container.inner-page-scroll-tabs + .prepend-top-default + .search-holder + = render 'shared/projects/search_form', autofocus: true, icon: true, admin_view: true + .dropdown + - toggle_text = 'Namespace' + - if params[:namespace_id].present? + = hidden_field_tag :namespace_id, params[:namespace_id] + - namespace = Namespace.find(params[:namespace_id]) + - toggle_text = "#{namespace.kind}: #{namespace.full_path}" + = dropdown_toggle(toggle_text, { toggle: 'dropdown', is_filter: 'true' }, { toggle_class: 'js-namespace-select large' }) + .dropdown-menu.dropdown-select.dropdown-menu-right + = dropdown_title('Namespaces') + = dropdown_filter("Search for Namespace") + = dropdown_content + = dropdown_loading + = render 'shared/projects/dropdown' + = link_to new_project_path, class: 'btn btn-success' do + New Project + = button_tag "Search", class: "btn btn-primary btn-search hide" -%div{ class: container_class } - .top-area.scrolling-tabs-container.inner-page-scroll-tabs - .prepend-top-default - .search-holder - = render 'shared/projects/search_form', autofocus: true, icon: true, admin_view: true - .dropdown - - toggle_text = 'Namespace' - - if params[:namespace_id].present? - = hidden_field_tag :namespace_id, params[:namespace_id] - - namespace = Namespace.find(params[:namespace_id]) - - toggle_text = "#{namespace.kind}: #{namespace.full_path}" - = dropdown_toggle(toggle_text, { toggle: 'dropdown', is_filter: 'true' }, { toggle_class: 'js-namespace-select large' }) - .dropdown-menu.dropdown-select.dropdown-menu-right - = dropdown_title('Namespaces') - = dropdown_filter("Search for Namespace") - = dropdown_content - = dropdown_loading - = render 'shared/projects/dropdown' - = link_to new_project_path, class: 'btn btn-success' do - New Project - = button_tag "Search", class: "btn btn-primary btn-search hide" + %ul.nav-links.nav.nav-tabs + - opts = params[:visibility_level].present? ? {} : { page: admin_projects_path } + = nav_link(opts) do + = link_to admin_projects_path do + All - %ul.nav-links.nav.nav-tabs - - opts = params[:visibility_level].present? ? {} : { page: admin_projects_path } - = nav_link(opts) do - = link_to admin_projects_path do - All + = nav_link(html_options: { class: active_when(params[:visibility_level] == Gitlab::VisibilityLevel::PRIVATE.to_s) }) do + = link_to admin_projects_path(visibility_level: Gitlab::VisibilityLevel::PRIVATE) do + Private + = nav_link(html_options: { class: active_when(params[:visibility_level] == Gitlab::VisibilityLevel::INTERNAL.to_s) }) do + = link_to admin_projects_path(visibility_level: Gitlab::VisibilityLevel::INTERNAL) do + Internal + = nav_link(html_options: { class: active_when(params[:visibility_level] == Gitlab::VisibilityLevel::PUBLIC.to_s) }) do + = link_to admin_projects_path(visibility_level: Gitlab::VisibilityLevel::PUBLIC) do + Public - = nav_link(html_options: { class: active_when(params[:visibility_level] == Gitlab::VisibilityLevel::PRIVATE.to_s) }) do - = link_to admin_projects_path(visibility_level: Gitlab::VisibilityLevel::PRIVATE) do - Private - = nav_link(html_options: { class: active_when(params[:visibility_level] == Gitlab::VisibilityLevel::INTERNAL.to_s) }) do - = link_to admin_projects_path(visibility_level: Gitlab::VisibilityLevel::INTERNAL) do - Internal - = nav_link(html_options: { class: active_when(params[:visibility_level] == Gitlab::VisibilityLevel::PUBLIC.to_s) }) do - = link_to admin_projects_path(visibility_level: Gitlab::VisibilityLevel::PUBLIC) do - Public - - = render 'projects' += render 'projects' diff --git a/app/views/admin/requests_profiles/index.html.haml b/app/views/admin/requests_profiles/index.html.haml index 86bfeef580c..efc16bb4d3b 100644 --- a/app/views/admin/requests_profiles/index.html.haml +++ b/app/views/admin/requests_profiles/index.html.haml @@ -1,26 +1,24 @@ -- @no_container = true - page_title 'Requests Profiles' -%div{ class: container_class } - %h3.page-title - = page_title +%h3.page-title + = page_title - .bs-callout.clearfix - Pass the header - %code X-Profile-Token: #{@profile_token} - to profile the request +.bs-callout.clearfix + Pass the header + %code X-Profile-Token: #{@profile_token} + to profile the request - - if @profiles.present? - .prepend-top-default - - @profiles.each do |path, profiles| - .card.card-small - .card-header - %code= path - %ul.content-list - - profiles.each do |profile| - %li - = link_to profile.time.to_s(:long) + ' ' + profile.profile_mode.capitalize, - admin_requests_profile_path(profile) - - else - %p - No profiles found +- if @profiles.present? + .prepend-top-default + - @profiles.each do |path, profiles| + .card.card-small + .card-header + %code= path + %ul.content-list + - profiles.each do |profile| + %li + = link_to profile.time.to_s(:long) + ' ' + profile.profile_mode.capitalize, + admin_requests_profile_path(profile) +- else + %p + No profiles found diff --git a/app/views/admin/runners/index.html.haml b/app/views/admin/runners/index.html.haml index 5129f5d193b..76af4189b5b 100644 --- a/app/views/admin/runners/index.html.haml +++ b/app/views/admin/runners/index.html.haml @@ -1,96 +1,87 @@ - breadcrumb_title _('Runners') -- @no_container = true -%div{ class: container_class } - .row - .col-sm-6 - .bs-callout - %p - = (_"A 'Runner' is a process which runs a job. You can set up as many Runners as you need.") - %br - = _('Runners can be placed on separate users, servers, even on your local machine.') - %br +.row + .col-sm-6 + .bs-callout + %p + = (_"A 'Runner' is a process which runs a job. You can set up as many Runners as you need.") + %br + = _('Runners can be placed on separate users, servers, even on your local machine.') + %br - %div - %span= _('Each Runner can be in one of the following states:') - %ul - %li - %span.badge.badge-success shared - \- - = _('Runner runs jobs from all unassigned projects') - %li - %span.badge.badge-success group - \- - = _('Runner runs jobs from all unassigned projects in its group') - %li - %span.badge.badge-info specific - \- - = _('Runner runs jobs from assigned projects') - %li - %span.badge.badge-warning locked - \- - = _('Runner cannot be assigned to other projects') - %li - %span.badge.badge-danger paused - \- - = _('Runner will not receive any new jobs') + %div + %span= _('Each Runner can be in one of the following states:') + %ul + %li + %span.badge.badge-success shared + \- + = _('Runner runs jobs from all unassigned projects') + %li + %span.badge.badge-success group + \- + = _('Runner runs jobs from all unassigned projects in its group') + %li + %span.badge.badge-info specific + \- + = _('Runner runs jobs from assigned projects') + %li + %span.badge.badge-warning locked + \- + = _('Runner cannot be assigned to other projects') + %li + %span.badge.badge-danger paused + \- + = _('Runner will not receive any new jobs') - .col-sm-6 - .bs-callout - = render partial: 'ci/runner/how_to_setup_runner', - locals: { registration_token: Gitlab::CurrentSettings.runners_registration_token, - type: 'shared', - reset_token_url: reset_registration_token_admin_application_settings_path } + .col-sm-6 + .bs-callout + = render partial: 'ci/runner/how_to_setup_runner', + locals: { registration_token: Gitlab::CurrentSettings.runners_registration_token, + type: 'shared', + reset_token_url: reset_registration_token_admin_application_settings_path } - .row - .col-sm-9 - = form_tag admin_runners_path, id: 'runners-search', method: :get, class: 'filter-form js-filter-form' do - .filtered-search-wrapper - .filtered-search-box - = dropdown_tag(custom_icon('icon_history'), - options: { wrapper_class: 'filtered-search-history-dropdown-wrapper', - toggle_class: 'filtered-search-history-dropdown-toggle-button', - dropdown_class: 'filtered-search-history-dropdown', - content_class: 'filtered-search-history-dropdown-content', - title: _('Recent searches') }) do - .js-filtered-search-history-dropdown{ data: { full_path: admin_runners_path } } - .filtered-search-box-input-container.droplab-dropdown - .scroll-container - %ul.tokens-container.list-unstyled - %li.input-token - %input.form-control.filtered-search{ search_filter_input_options('runners') } - #js-dropdown-hint.filtered-search-input-dropdown-menu.dropdown-menu.hint-dropdown - %ul{ data: { dropdown: true } } - %li.filter-dropdown-item{ data: { action: 'submit' } } - = button_tag class: %w[btn btn-link] do - = sprite_icon('search') - %span - = _('Press Enter or click to search') - %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } } - %li.filter-dropdown-item - = button_tag class: %w[btn btn-link] do - -# Encapsulate static class name `{{icon}}` inside #{} to bypass - -# haml lint's ClassAttributeWithStaticValue - %svg - %use{ 'xlink:href': "#{'{{icon}}'}" } - %span.js-filter-hint - {{hint}} - %span.js-filter-tag.dropdown-light-content - {{tag}} - - #js-dropdown-admin-runner-status.filtered-search-input-dropdown-menu.dropdown-menu - %ul{ data: { dropdown: true } } - - Ci::Runner::AVAILABLE_STATUSES.each do |status| - %li.filter-dropdown-item{ data: { value: status } } - = button_tag class: %w[btn btn-link] do - = status.titleize +.row + .col-sm-9 + = form_tag admin_runners_path, id: 'runners-search', method: :get, class: 'filter-form js-filter-form' do + .filtered-search-wrapper + .filtered-search-box + = dropdown_tag(custom_icon('icon_history'), + options: { wrapper_class: 'filtered-search-history-dropdown-wrapper', + toggle_class: 'filtered-search-history-dropdown-toggle-button', + dropdown_class: 'filtered-search-history-dropdown', + content_class: 'filtered-search-history-dropdown-content', + title: _('Recent searches') }) do + .js-filtered-search-history-dropdown{ data: { full_path: admin_runners_path } } + .filtered-search-box-input-container.droplab-dropdown + .scroll-container + %ul.tokens-container.list-unstyled + %li.input-token + %input.form-control.filtered-search{ search_filter_input_options('runners') } + #js-dropdown-hint.filtered-search-input-dropdown-menu.dropdown-menu.hint-dropdown + %ul{ data: { dropdown: true } } + %li.filter-dropdown-item{ data: { action: 'submit' } } + = button_tag class: %w[btn btn-link] do + = sprite_icon('search') + %span + = _('Press Enter or click to search') + %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } } + %li.filter-dropdown-item + = button_tag class: %w[btn btn-link] do + -# Encapsulate static class name `{{icon}}` inside #{} to bypass + -# haml lint's ClassAttributeWithStaticValue + %svg + %use{ 'xlink:href': "#{'{{icon}}'}" } + %span.js-filter-hint + {{hint}} + %span.js-filter-tag.dropdown-light-content + {{tag}} - #js-dropdown-admin-runner-type.filtered-search-input-dropdown-menu.dropdown-menu - %ul{ data: { dropdown: true } } - - Ci::Runner::AVAILABLE_TYPES.each do |runner_type| - %li.filter-dropdown-item{ data: { value: runner_type } } - = button_tag class: %w[btn btn-link] do - = runner_type.titleize + #js-dropdown-admin-runner-status.filtered-search-input-dropdown-menu.dropdown-menu + %ul{ data: { dropdown: true } } + - Ci::Runner::AVAILABLE_STATUSES.each do |status| + %li.filter-dropdown-item{ data: { value: status } } + = button_tag class: %w[btn btn-link] do + = status.titleize #js-dropdown-admin-runner-type.filtered-search-input-dropdown-menu.dropdown-menu %ul{ data: { dropdown: true } } @@ -99,43 +90,50 @@ = button_tag class: %w[btn btn-link] do = runner_type.titleize - #js-dropdown-runner-tag.filtered-search-input-dropdown-menu.dropdown-menu - %ul{ data: { dropdown: true } } - %li.filter-dropdown-item{ data: { value: 'none' } } - %button.btn.btn-link - = _('No Tag') - %li.divider.droplab-item-ignore - %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } } - %li.filter-dropdown-item - %button.btn.btn-link.js-data-value - %span.dropdown-light-content - {{name}} + #js-dropdown-admin-runner-type.filtered-search-input-dropdown-menu.dropdown-menu + %ul{ data: { dropdown: true } } + - Ci::Runner::AVAILABLE_TYPES.each do |runner_type| + %li.filter-dropdown-item{ data: { value: runner_type } } + = button_tag class: %w[btn btn-link] do + = runner_type.titleize + + #js-dropdown-runner-tag.filtered-search-input-dropdown-menu.dropdown-menu + %ul{ data: { dropdown: true } } + %li.filter-dropdown-item{ data: { value: 'none' } } + %button.btn.btn-link + = _('No Tag') + %li.divider.droplab-item-ignore + %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } } + %li.filter-dropdown-item + %button.btn.btn-link.js-data-value + %span.dropdown-light-content + {{name}} - = button_tag class: %w[clear-search hidden] do - = icon('times') - .filter-dropdown-container - = render 'sort_dropdown' + = button_tag class: %w[clear-search hidden] do + = icon('times') + .filter-dropdown-container + = render 'sort_dropdown' - .col-sm-3.text-right-lg - = _('Runners currently online: %{active_runners_count}') % { active_runners_count: @active_runners_count } + .col-sm-3.text-right-lg + = _('Runners currently online: %{active_runners_count}') % { active_runners_count: @active_runners_count } - - if @runners.any? - .runners-content.content-list - .table-holder - .gl-responsive-table-row.table-row-header{ role: 'row' } - .table-section.section-10{ role: 'rowheader' }= _('Type') - .table-section.section-10{ role: 'rowheader' }= _('Runner token') - .table-section.section-20{ role: 'rowheader' }= _('Description') - .table-section.section-10{ role: 'rowheader' }= _('Version') - .table-section.section-10{ role: 'rowheader' }= _('IP Address') - .table-section.section-5{ role: 'rowheader' }= _('Projects') - .table-section.section-5{ role: 'rowheader' }= _('Jobs') - .table-section.section-10{ role: 'rowheader' }= _('Tags') - .table-section.section-10{ role: 'rowheader' }= _('Last contact') - .table-section.section-10{ role: 'rowheader' } +- if @runners.any? + .runners-content.content-list + .table-holder + .gl-responsive-table-row.table-row-header{ role: 'row' } + .table-section.section-10{ role: 'rowheader' }= _('Type') + .table-section.section-10{ role: 'rowheader' }= _('Runner token') + .table-section.section-20{ role: 'rowheader' }= _('Description') + .table-section.section-10{ role: 'rowheader' }= _('Version') + .table-section.section-10{ role: 'rowheader' }= _('IP Address') + .table-section.section-5{ role: 'rowheader' }= _('Projects') + .table-section.section-5{ role: 'rowheader' }= _('Jobs') + .table-section.section-10{ role: 'rowheader' }= _('Tags') + .table-section.section-10{ role: 'rowheader' }= _('Last contact') + .table-section.section-10{ role: 'rowheader' } - - @runners.each do |runner| - = render 'admin/runners/runner', runner: runner - = paginate @runners, theme: 'gitlab' - - else - .nothing-here-block= _('No runners found') + - @runners.each do |runner| + = render 'admin/runners/runner', runner: runner + = paginate @runners, theme: 'gitlab' +- else + .nothing-here-block= _('No runners found') diff --git a/app/views/admin/system_info/show.html.haml b/app/views/admin/system_info/show.html.haml index b19934e028d..948a11646f7 100644 --- a/app/views/admin/system_info/show.html.haml +++ b/app/views/admin/system_info/show.html.haml @@ -1,37 +1,35 @@ -- @no_container = true - page_title "System Info" -%div{ class: container_class } - .prepend-top-default - .row - .col-sm-4 - .card.bg-light.light-well - %h4 CPU - .data - - if @cpus - %h1 #{@cpus.length} cores - - else - = icon('warning', class: 'text-warning') - Unable to collect CPU info - .col-sm-4 - .card.bg-light.light-well - %h4 Memory Usage - .data - - if @memory - %h1 #{number_to_human_size(@memory.active_bytes)} / #{number_to_human_size(@memory.total_bytes)} - - else - = icon('warning', class: 'text-warning') - Unable to collect memory info - .col-sm-4 - .card.bg-light.light-well - %h4 Disk Usage - .data - - @disks.each do |disk| - %h1 #{number_to_human_size(disk[:bytes_used])} / #{number_to_human_size(disk[:bytes_total])} - %p= disk[:disk_name] - %p= disk[:mount_path] - .col-sm-4 - .card.bg-light.light-well - %h4 Uptime - .data - %h1= distance_of_time_in_words_to_now(Rails.application.config.booted_at) +.prepend-top-default +.row + .col-sm-4 + .card.bg-light.light-well + %h4 CPU + .data + - if @cpus + %h1 #{@cpus.length} cores + - else + = icon('warning', class: 'text-warning') + Unable to collect CPU info + .col-sm-4 + .card.bg-light.light-well + %h4 Memory Usage + .data + - if @memory + %h1 #{number_to_human_size(@memory.active_bytes)} / #{number_to_human_size(@memory.total_bytes)} + - else + = icon('warning', class: 'text-warning') + Unable to collect memory info + .col-sm-4 + .card.bg-light.light-well + %h4 Disk Usage + .data + - @disks.each do |disk| + %h1 #{number_to_human_size(disk[:bytes_used])} / #{number_to_human_size(disk[:bytes_total])} + %p= disk[:disk_name] + %p= disk[:mount_path] + .col-sm-4 + .card.bg-light.light-well + %h4 Uptime + .data + %h1= distance_of_time_in_words_to_now(Rails.application.config.booted_at) diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml index 6fc7ec1bb6f..36b62557fa6 100644 --- a/app/views/admin/users/index.html.haml +++ b/app/views/admin/users/index.html.haml @@ -1,79 +1,77 @@ -- @no_container = true - page_title "Users" -%div{ class: container_class } - .top-area.scrolling-tabs-container.inner-page-scroll-tabs - .fade-left - = icon('angle-left') - .fade-right - = icon('angle-right') - %ul.nav-links.nav.nav-tabs.scrolling-tabs - = nav_link(html_options: { class: active_when(params[:filter].nil?) }) do - = link_to admin_users_path do - = s_('AdminUsers|Active') - %small.badge.badge-pill= limited_counter_with_delimiter(User.active) - = nav_link(html_options: { class: active_when(params[:filter] == 'admins') }) do - = link_to admin_users_path(filter: "admins") do - = s_('AdminUsers|Admins') - %small.badge.badge-pill= limited_counter_with_delimiter(User.admins) - = nav_link(html_options: { class: "#{active_when(params[:filter] == 'two_factor_enabled')} filter-two-factor-enabled" }) do - = link_to admin_users_path(filter: 'two_factor_enabled') do - = s_('AdminUsers|2FA Enabled') - %small.badge.badge-pill= limited_counter_with_delimiter(User.with_two_factor) - = nav_link(html_options: { class: "#{active_when(params[:filter] == 'two_factor_disabled')} filter-two-factor-disabled" }) do - = link_to admin_users_path(filter: 'two_factor_disabled') do - = s_('AdminUsers|2FA Disabled') - %small.badge.badge-pill= limited_counter_with_delimiter(User.without_two_factor) - = nav_link(html_options: { class: active_when(params[:filter] == 'external') }) do - = link_to admin_users_path(filter: 'external') do - = s_('AdminUsers|External') - %small.badge.badge-pill= limited_counter_with_delimiter(User.external) - = nav_link(html_options: { class: active_when(params[:filter] == 'blocked') }) do - = link_to admin_users_path(filter: "blocked") do - = s_('AdminUsers|Blocked') - %small.badge.badge-pill= limited_counter_with_delimiter(User.blocked) - = nav_link(html_options: { class: active_when(params[:filter] == 'wop') }) do - = link_to admin_users_path(filter: "wop") do - = s_('AdminUsers|Without projects') - %small.badge.badge-pill= limited_counter_with_delimiter(User.without_projects) - .nav-controls - = render_if_exists 'admin/users/admin_email_users' - = link_to s_('AdminUsers|New user'), new_admin_user_path, class: 'btn btn-success btn-search float-right' +.top-area.scrolling-tabs-container.inner-page-scroll-tabs + .fade-left + = icon('angle-left') + .fade-right + = icon('angle-right') + %ul.nav-links.nav.nav-tabs.scrolling-tabs + = nav_link(html_options: { class: active_when(params[:filter].nil?) }) do + = link_to admin_users_path do + = s_('AdminUsers|Active') + %small.badge.badge-pill= limited_counter_with_delimiter(User.active) + = nav_link(html_options: { class: active_when(params[:filter] == 'admins') }) do + = link_to admin_users_path(filter: "admins") do + = s_('AdminUsers|Admins') + %small.badge.badge-pill= limited_counter_with_delimiter(User.admins) + = nav_link(html_options: { class: "#{active_when(params[:filter] == 'two_factor_enabled')} filter-two-factor-enabled" }) do + = link_to admin_users_path(filter: 'two_factor_enabled') do + = s_('AdminUsers|2FA Enabled') + %small.badge.badge-pill= limited_counter_with_delimiter(User.with_two_factor) + = nav_link(html_options: { class: "#{active_when(params[:filter] == 'two_factor_disabled')} filter-two-factor-disabled" }) do + = link_to admin_users_path(filter: 'two_factor_disabled') do + = s_('AdminUsers|2FA Disabled') + %small.badge.badge-pill= limited_counter_with_delimiter(User.without_two_factor) + = nav_link(html_options: { class: active_when(params[:filter] == 'external') }) do + = link_to admin_users_path(filter: 'external') do + = s_('AdminUsers|External') + %small.badge.badge-pill= limited_counter_with_delimiter(User.external) + = nav_link(html_options: { class: active_when(params[:filter] == 'blocked') }) do + = link_to admin_users_path(filter: "blocked") do + = s_('AdminUsers|Blocked') + %small.badge.badge-pill= limited_counter_with_delimiter(User.blocked) + = nav_link(html_options: { class: active_when(params[:filter] == 'wop') }) do + = link_to admin_users_path(filter: "wop") do + = s_('AdminUsers|Without projects') + %small.badge.badge-pill= limited_counter_with_delimiter(User.without_projects) + .nav-controls + = render_if_exists 'admin/users/admin_email_users' + = link_to s_('AdminUsers|New user'), new_admin_user_path, class: 'btn btn-success btn-search float-right' - .filtered-search-block.row-content-block.border-top-0 - = form_tag admin_users_path, method: :get do - - if params[:filter].present? - = hidden_field_tag "filter", h(params[:filter]) - .search-holder - .search-field-holder - = search_field_tag :search_query, params[:search_query], placeholder: s_('AdminUsers|Search by name, email or username'), class: 'form-control search-text-input js-search-input', spellcheck: false - - if @sort.present? - = hidden_field_tag :sort, @sort - = icon("search", class: "search-icon") - = button_tag s_('AdminUsers|Search users') if Rails.env.test? - .dropdown.user-sort-dropdown - - toggle_text = @sort.present? ? users_sort_options_hash[@sort] : sort_title_name - = dropdown_toggle(toggle_text, { toggle: 'dropdown' }) - %ul.dropdown-menu.dropdown-menu-right - %li.dropdown-header - = s_('AdminUsers|Sort by') - %li - - users_sort_options_hash.each do |value, title| - = link_to admin_users_path(sort: value, filter: params[:filter], search_query: params[:search_query]) do - = title +.filtered-search-block.row-content-block.border-top-0 + = form_tag admin_users_path, method: :get do + - if params[:filter].present? + = hidden_field_tag "filter", h(params[:filter]) + .search-holder + .search-field-holder + = search_field_tag :search_query, params[:search_query], placeholder: s_('AdminUsers|Search by name, email or username'), class: 'form-control search-text-input js-search-input', spellcheck: false + - if @sort.present? + = hidden_field_tag :sort, @sort + = icon("search", class: "search-icon") + = button_tag s_('AdminUsers|Search users') if Rails.env.test? + .dropdown.user-sort-dropdown + - toggle_text = @sort.present? ? users_sort_options_hash[@sort] : sort_title_name + = dropdown_toggle(toggle_text, { toggle: 'dropdown' }) + %ul.dropdown-menu.dropdown-menu-right + %li.dropdown-header + = s_('AdminUsers|Sort by') + %li + - users_sort_options_hash.each do |value, title| + = link_to admin_users_path(sort: value, filter: params[:filter], search_query: params[:search_query]) do + = title - - if @users.empty? - .nothing-here-block.border-top-0 - = s_('AdminUsers|No users found') - - else - .table-holder - .thead-white.text-nowrap.gl-responsive-table-row.table-row-header{ role: 'row' } - .table-section.section-40{ role: 'rowheader' }= _('Name') - .table-section.section-25{ role: 'rowheader' }= _('Created on') - .table-section.section-15{ role: 'rowheader' }= _('Last activity') +- if @users.empty? + .nothing-here-block.border-top-0 + = s_('AdminUsers|No users found') +- else + .table-holder + .thead-white.text-nowrap.gl-responsive-table-row.table-row-header{ role: 'row' } + .table-section.section-40{ role: 'rowheader' }= _('Name') + .table-section.section-25{ role: 'rowheader' }= _('Created on') + .table-section.section-15{ role: 'rowheader' }= _('Last activity') - = render partial: 'admin/users/user', collection: @users + = render partial: 'admin/users/user', collection: @users - = paginate @users, theme: "gitlab" += paginate @users, theme: "gitlab" #delete-user-modal diff --git a/app/views/dashboard/activity.html.haml b/app/views/dashboard/activity.html.haml index b1c192d7bad..d7306f5932d 100644 --- a/app/views/dashboard/activity.html.haml +++ b/app/views/dashboard/activity.html.haml @@ -1,18 +1,15 @@ - @hide_top_links = true -- @no_container = true = content_for :meta_tags do = auto_discovery_link_tag(:atom, dashboard_projects_url(rss_url_options), title: "All activity") - = render_dashboard_gold_trial(current_user) - page_title "Activity" - header_title "Activity", activity_dashboard_path -%div{ class: container_class } - = render "projects/last_push" - = render 'dashboard/activity_head' += render "projects/last_push" += render 'dashboard/activity_head' - %section.activities - = render 'activities' +%section.activities + = render 'activities' diff --git a/app/views/dashboard/projects/index.html.haml b/app/views/dashboard/projects/index.html.haml index 0298f539b4b..d2aa07bab22 100644 --- a/app/views/dashboard/projects/index.html.haml +++ b/app/views/dashboard/projects/index.html.haml @@ -1,4 +1,3 @@ -- @no_container = true - @hide_top_links = true = content_for :meta_tags do @@ -9,11 +8,10 @@ - page_title "Projects" - header_title "Projects", dashboard_projects_path -%div{ class: container_class } - = render "projects/last_push" - - if show_projects?(@projects, params) - = render 'dashboard/projects_head' - = render 'nav' unless Feature.enabled?(:project_list_filter_bar) - = render 'projects' - - else - = render "zero_authorized_projects" += render "projects/last_push" +- if show_projects?(@projects, params) + = render 'dashboard/projects_head' + = render 'nav' unless Feature.enabled?(:project_list_filter_bar) + = render 'projects' +- else + = render "zero_authorized_projects" diff --git a/app/views/dashboard/projects/starred.html.haml b/app/views/dashboard/projects/starred.html.haml index 0fcc6894b68..2924918aa4f 100644 --- a/app/views/dashboard/projects/starred.html.haml +++ b/app/views/dashboard/projects/starred.html.haml @@ -1,16 +1,14 @@ - @hide_top_links = true -- @no_container = true - breadcrumb_title _("Projects") - page_title _("Starred Projects") - header_title _("Projects"), dashboard_projects_path = render_dashboard_gold_trial(current_user) -%div{ class: container_class } - = render "projects/last_push" - = render 'dashboard/projects_head', project_tab_filter: :starred += render "projects/last_push" += render 'dashboard/projects_head', project_tab_filter: :starred - - if params[:filter_projects] || any_projects?(@projects) - = render 'projects' - - else - = render 'starred_empty_state' +- if params[:filter_projects] || any_projects?(@projects) + = render 'projects' +- else + = render 'starred_empty_state' diff --git a/app/views/groups/labels/index.html.haml b/app/views/groups/labels/index.html.haml index a8358704b03..41c1d3e84b7 100644 --- a/app/views/groups/labels/index.html.haml +++ b/app/views/groups/labels/index.html.haml @@ -1,4 +1,3 @@ -- @no_container = true - page_title 'Labels' - can_admin_label = can?(current_user, :admin_label, @group) - search = params[:search] @@ -7,24 +6,23 @@ - if labels_or_filters #promote-label-modal - %div{ class: container_class } - = render 'shared/labels/nav', labels_or_filters: labels_or_filters, can_admin_label: can_admin_label + = render 'shared/labels/nav', labels_or_filters: labels_or_filters, can_admin_label: can_admin_label - .labels-container.prepend-top-5 - - if @labels.any? - .text-muted - = _('Labels can be applied to %{features}. Group labels are available for any project within the group.') % { features: issuable_types.to_sentence } - .other-labels - %h5= _('Labels') - %ul.content-list.manage-labels-list.js-other-labels - = render partial: 'shared/label', collection: @labels, as: :label, locals: { use_label_priority: false, subject: @group } - = paginate @labels, theme: 'gitlab' - - elsif search.present? - .nothing-here-block - = _('No labels with such name or description') - - elsif subscribed.present? - .nothing-here-block - = _('You do not have any subscriptions yet') + .labels-container.prepend-top-5 + - if @labels.any? + .text-muted + = _('Labels can be applied to %{features}. Group labels are available for any project within the group.') % { features: issuable_types.to_sentence } + .other-labels + %h5= _('Labels') + %ul.content-list.manage-labels-list.js-other-labels + = render partial: 'shared/label', collection: @labels, as: :label, locals: { use_label_priority: false, subject: @group } + = paginate @labels, theme: 'gitlab' + - elsif search.present? + .nothing-here-block + = _('No labels with such name or description') + - elsif subscribed.present? + .nothing-here-block + = _('You do not have any subscriptions yet') - else = render 'shared/empty_states/labels' diff --git a/app/views/groups/milestones/new.html.haml b/app/views/groups/milestones/new.html.haml index 248cb3b0ba5..2c93b0e4efd 100644 --- a/app/views/groups/milestones/new.html.haml +++ b/app/views/groups/milestones/new.html.haml @@ -1,12 +1,10 @@ -- @no_container = true - add_to_breadcrumbs _("Milestones"), group_milestones_path(@group) - breadcrumb_title _("New") - page_title _("Milestones"), @milestone.name, _("Milestones") -%div{ class: container_class } - %h3.page-title - New Milestone +%h3.page-title + New Milestone - %hr +%hr - = render "form" += render "form" diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml index 255a9ad038c..0e6c16f0f06 100644 --- a/app/views/groups/show.html.haml +++ b/app/views/groups/show.html.haml @@ -1,11 +1,10 @@ -- @no_container = true - breadcrumb_title _("Details") - @content_class = "limit-container-width" unless fluid_layout = content_for :meta_tags do = auto_discovery_link_tag(:atom, group_url(@group, rss_url_options), title: "#{@group.name} activity") -%div{ class: [container_class, ("limit-container-width" unless fluid_layout)] } +%div{ class: [("limit-container-width" unless fluid_layout)] } = render 'groups/home_panel' .groups-listing{ data: { endpoints: { default: group_children_path(@group, format: :json), shared: group_shared_projects_path(@group, format: :json) } } } diff --git a/app/views/instance_statistics/cohorts/index.html.haml b/app/views/instance_statistics/cohorts/index.html.haml index e135bab10d8..c438566cb05 100644 --- a/app/views/instance_statistics/cohorts/index.html.haml +++ b/app/views/instance_statistics/cohorts/index.html.haml @@ -1,16 +1,14 @@ - breadcrumb_title _("Cohorts") -- @no_container = true -%div{ class: container_class } - - if @cohorts - = render 'cohorts_table' - - else - .bs-callout.bs-callout-warning.clearfix - %p - - usage_ping_path = help_page_path('user/admin_area/settings/usage_statistics', anchor: 'usage-ping') - - usage_ping_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: usage_ping_path } - = s_('User Cohorts are only shown when the %{usage_ping_link_start}usage ping%{usage_ping_link_end} is enabled.').html_safe % { usage_ping_link_start: usage_ping_link_start, usage_ping_link_end: '</a>'.html_safe } - - if current_user.admin? - - application_settings_path = admin_application_settings_path(anchor: 'usage-statistics') - - application_settings_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: application_settings_path } - = s_('To enable it and see User Cohorts, visit %{application_settings_link_start}application settings%{application_settings_link_end}.').html_safe % { application_settings_link_start: application_settings_link_start, application_settings_link_end: '</a>'.html_safe } +- if @cohorts + = render 'cohorts_table' +- else + .bs-callout.bs-callout-warning.clearfix + %p + - usage_ping_path = help_page_path('user/admin_area/settings/usage_statistics', anchor: 'usage-ping') + - usage_ping_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: usage_ping_path } + = s_('User Cohorts are only shown when the %{usage_ping_link_start}usage ping%{usage_ping_link_end} is enabled.').html_safe % { usage_ping_link_start: usage_ping_link_start, usage_ping_link_end: '</a>'.html_safe } + - if current_user.admin? + - application_settings_path = admin_application_settings_path(anchor: 'usage-statistics') + - application_settings_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: application_settings_path } + = s_('To enable it and see User Cohorts, visit %{application_settings_link_start}application settings%{application_settings_link_end}.').html_safe % { application_settings_link_start: application_settings_link_start, application_settings_link_end: '</a>'.html_safe } diff --git a/app/views/instance_statistics/conversational_development_index/index.html.haml b/app/views/instance_statistics/conversational_development_index/index.html.haml index 23f90b876a0..49c8fdc9630 100644 --- a/app/views/instance_statistics/conversational_development_index/index.html.haml +++ b/app/views/instance_statistics/conversational_development_index/index.html.haml @@ -1,4 +1,3 @@ -- @no_container = true - page_title _('ConvDev Index') - usage_ping_enabled = Gitlab::CurrentSettings.usage_ping_enabled diff --git a/app/views/layouts/_flash.html.haml b/app/views/layouts/_flash.html.haml index 2cdaa85bdaa..d673d7164b3 100644 --- a/app/views/layouts/_flash.html.haml +++ b/app/views/layouts/_flash.html.haml @@ -1,10 +1,8 @@ -- extra_flash_class = local_assigns.fetch(:extra_flash_class, nil) - -.flash-container.flash-container-page +.flash-container.flash-container-page.sticky -# We currently only support `alert`, `notice`, `success` - flash.each do |key, value| -# Don't show a flash message if the message is nil - if value - %div{ class: "flash-#{key}" } - %div{ class: "#{(container_class unless fluid_layout)} #{(extra_flash_class unless @no_container)} #{@content_class}" } - %span= value + %div{ class: "flash-content flash-#{key} rounded" } + %span= value + = sprite_icon('close', size: 16, css_class: 'close-icon') diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml index 006334ade07..443a73f5cce 100644 --- a/app/views/layouts/_page.html.haml +++ b/app/views/layouts/_page.html.haml @@ -13,8 +13,8 @@ = render "shared/ping_consent" - unless @hide_breadcrumbs = render "layouts/nav/breadcrumbs" - = render "layouts/flash", extra_flash_class: 'limit-container-width' .d-flex %div{ class: "#{(container_class unless @no_container)} #{@content_class}" } .content{ id: "content-body" } + = render "layouts/flash", extra_flash_class: 'limit-container-width' = yield diff --git a/app/views/projects/activity.html.haml b/app/views/projects/activity.html.haml index b28a375e956..6a4760c3954 100644 --- a/app/views/projects/activity.html.haml +++ b/app/views/projects/activity.html.haml @@ -1,8 +1,4 @@ -- @no_container = true - - page_title _("Activity") -%div{ class: container_class } - = render 'projects/last_push' - += render 'projects/last_push' = render 'projects/activity' diff --git a/app/views/projects/blame/show.html.haml b/app/views/projects/blame/show.html.haml index ef6f5c76de6..f2215765974 100644 --- a/app/views/projects/blame/show.html.haml +++ b/app/views/projects/blame/show.html.haml @@ -1,44 +1,42 @@ -- @no_container = true - project_duration = age_map_duration(@blame_groups, @project) - page_title "Blame", @blob.path, @ref -%div{ class: container_class } - #blob-content-holder.tree-holder - = render "projects/blob/breadcrumb", blob: @blob, blame: true +#blob-content-holder.tree-holder + = render "projects/blob/breadcrumb", blob: @blob, blame: true - .file-holder - = render "projects/blob/header", blob: @blob, blame: true - .file-blame-legend - = render 'age_map_legend' - .table-responsive.file-content.blame.code.js-syntax-highlight - %table - - current_line = 1 - - @blame_groups.each do |blame_group| - %tr - - commit = blame_group[:commit] - %td.blame-commit{ class: age_map_class(commit.committed_date, project_duration) } - .commit - = author_avatar(commit, size: 36, has_tooltip: false) - .commit-row-title - %span.item-title.str-truncated-100 - = link_to_markdown commit.title, project_commit_path(@project, commit.id), class: "cdark", title: commit.title - .float-right - = link_to commit.short_id, project_commit_path(@project, commit), class: "commit-sha" - - .light - = commit_author_link(commit, avatar: false) - committed - #{time_ago_with_tooltip(commit.committed_date)} - %td.line-numbers - - line_count = blame_group[:lines].count - - (current_line...(current_line + line_count)).each do |i| - %a.diff-line-num{ href: "#L#{i}", id: "L#{i}", 'data-line-number' => i } - = icon("link") - = i - \ - - current_line += line_count - %td.lines - %pre.code.highlight - %code - - blame_group[:lines].each do |line| - #{line} + .file-holder + = render "projects/blob/header", blob: @blob, blame: true + .file-blame-legend + = render 'age_map_legend' + .table-responsive.file-content.blame.code.js-syntax-highlight + %table + - current_line = 1 + - @blame_groups.each do |blame_group| + %tr + - commit = blame_group[:commit] + %td.blame-commit{ class: age_map_class(commit.committed_date, project_duration) } + .commit + = author_avatar(commit, size: 36, has_tooltip: false) + .commit-row-title + %span.item-title.str-truncated-100 + = link_to_markdown commit.title, project_commit_path(@project, commit.id), class: "cdark", title: commit.title + .float-right + = link_to commit.short_id, project_commit_path(@project, commit), class: "commit-sha" + + .light + = commit_author_link(commit, avatar: false) + committed + #{time_ago_with_tooltip(commit.committed_date)} + %td.line-numbers + - line_count = blame_group[:lines].count + - (current_line...(current_line + line_count)).each do |i| + %a.diff-line-num{ href: "#L#{i}", id: "L#{i}", 'data-line-number' => i } + = icon("link") + = i + \ + - current_line += line_count + %td.lines + %pre.code.highlight + %code + - blame_group[:lines].each do |line| + #{line} diff --git a/app/views/projects/blob/edit.html.haml b/app/views/projects/blob/edit.html.haml index 4520cca8cf5..51e42091ab8 100644 --- a/app/views/projects/blob/edit.html.haml +++ b/app/views/projects/blob/edit.html.haml @@ -1,33 +1,31 @@ - breadcrumb_title "Repository" -- @no_container = true - page_title "Edit", @blob.path, @ref - content_for :page_specific_javascripts do = page_specific_javascript_tag('lib/ace.js') -%div{ class: container_class } - - if @conflict - .alert.alert-danger - Someone edited the file the same time you did. Please check out - = link_to "the file", project_blob_path(@project, tree_join(@branch_name, @file_path)), target: "_blank", rel: 'noopener noreferrer' - and make sure your changes will not unintentionally remove theirs. - .editor-title-row - %h3.page-title.blob-edit-page-title - Edit file - = render 'template_selectors' - .file-editor - %ul.nav-links.no-bottom.js-edit-mode.nav.nav-tabs - %li.active - = link_to '#editor' do - Write +- if @conflict + .alert.alert-danger + Someone edited the file the same time you did. Please check out + = link_to "the file", project_blob_path(@project, tree_join(@branch_name, @file_path)), target: "_blank", rel: 'noopener noreferrer' + and make sure your changes will not unintentionally remove theirs. +.editor-title-row + %h3.page-title.blob-edit-page-title + Edit file + = render 'template_selectors' +.file-editor + %ul.nav-links.no-bottom.js-edit-mode.nav.nav-tabs + %li.active + = link_to '#editor' do + Write - %li - = link_to '#preview', 'data-preview-url' => project_preview_blob_path(@project, @id) do - = editing_preview_title(@blob.name) + %li + = link_to '#preview', 'data-preview-url' => project_preview_blob_path(@project, @id) do + = editing_preview_title(@blob.name) - = form_tag(project_update_blob_path(@project, @id), method: :put, class: 'js-quick-submit js-requires-input js-edit-blob-form', data: blob_editor_paths(@project)) do - = render 'projects/blob/editor', ref: @ref, path: @path, blob_data: @blob.data - = render 'shared/new_commit_form', placeholder: "Update #{@blob.name}" - = hidden_field_tag 'last_commit_sha', @last_commit_sha - = hidden_field_tag 'content', '', id: "file-content" - = hidden_field_tag 'from_merge_request_iid', params[:from_merge_request_iid] - = render 'projects/commit_button', ref: @ref, cancel_path: project_blob_path(@project, @id) + = form_tag(project_update_blob_path(@project, @id), method: :put, class: 'js-quick-submit js-requires-input js-edit-blob-form', data: blob_editor_paths(@project)) do + = render 'projects/blob/editor', ref: @ref, path: @path, blob_data: @blob.data + = render 'shared/new_commit_form', placeholder: "Update #{@blob.name}" + = hidden_field_tag 'last_commit_sha', @last_commit_sha + = hidden_field_tag 'content', '', id: "file-content" + = hidden_field_tag 'from_merge_request_iid', params[:from_merge_request_iid] + = render 'projects/commit_button', ref: @ref, cancel_path: project_blob_path(@project, @id) diff --git a/app/views/projects/blob/show.html.haml b/app/views/projects/blob/show.html.haml index a0b0384d78d..688b8f001c3 100644 --- a/app/views/projects/blob/show.html.haml +++ b/app/views/projects/blob/show.html.haml @@ -1,19 +1,16 @@ - breadcrumb_title "Repository" -- @no_container = true - - page_title @blob.path, @ref - - signatures_path = namespace_project_signatures_path(namespace_id: @project.namespace.full_path, project_id: @project.path, id: @last_commit) + .js-signature-container{ data: { 'signatures-path': signatures_path } } -%div{ class: container_class } - = render 'projects/last_push' += render 'projects/last_push' - #tree-holder.tree-holder - = render 'blob', blob: @blob +#tree-holder.tree-holder + = render 'blob', blob: @blob - - if can_modify_blob?(@blob) - = render 'projects/blob/remove' + - if can_modify_blob?(@blob) + = render 'projects/blob/remove' - - title = "Replace #{@blob.name}" - = render 'projects/blob/upload', title: title, placeholder: title, button_title: 'Replace file', form_path: project_update_blob_path(@project, @id), method: :put + - title = "Replace #{@blob.name}" + = render 'projects/blob/upload', title: title, placeholder: title, button_title: 'Replace file', form_path: project_update_blob_path(@project, @id), method: :put diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml index 11340d12423..6bdc6f716fe 100644 --- a/app/views/projects/branches/index.html.haml +++ b/app/views/projects/branches/index.html.haml @@ -1,70 +1,68 @@ -- @no_container = true - page_title _('Branches') - add_to_breadcrumbs(_('Repository'), project_tree_path(@project)) -%div{ class: container_class } - .top-area.adjust - %ul.nav-links.issues-state-filters.nav.nav-tabs - %li{ class: active_when(@mode == 'overview') }> - = link_to s_('Branches|Overview'), project_branches_path(@project), title: s_('Branches|Show overview of the branches') +.top-area.adjust + %ul.nav-links.issues-state-filters.nav.nav-tabs + %li{ class: active_when(@mode == 'overview') }> + = link_to s_('Branches|Overview'), project_branches_path(@project), title: s_('Branches|Show overview of the branches') - %li{ class: active_when(@mode == 'active') }> - = link_to s_('Branches|Active'), project_branches_filtered_path(@project, state: 'active'), title: s_('Branches|Show active branches') + %li{ class: active_when(@mode == 'active') }> + = link_to s_('Branches|Active'), project_branches_filtered_path(@project, state: 'active'), title: s_('Branches|Show active branches') - %li{ class: active_when(@mode == 'stale') }> - = link_to s_('Branches|Stale'), project_branches_filtered_path(@project, state: 'stale'), title: s_('Branches|Show stale branches') + %li{ class: active_when(@mode == 'stale') }> + = link_to s_('Branches|Stale'), project_branches_filtered_path(@project, state: 'stale'), title: s_('Branches|Show stale branches') - %li{ class: active_when(!%w[overview active stale].include?(@mode)) }> - = link_to s_('Branches|All'), project_branches_filtered_path(@project, state: 'all'), title: s_('Branches|Show all branches') + %li{ class: active_when(!%w[overview active stale].include?(@mode)) }> + = link_to s_('Branches|All'), project_branches_filtered_path(@project, state: 'all'), title: s_('Branches|Show all branches') - .nav-controls - = form_tag(project_branches_filtered_path(@project, state: 'all'), method: :get) do - = search_field_tag :search, params[:search], { placeholder: s_('Branches|Filter by branch name'), id: 'branch-search', class: 'form-control search-text-input input-short', spellcheck: false } + .nav-controls + = form_tag(project_branches_filtered_path(@project, state: 'all'), method: :get) do + = search_field_tag :search, params[:search], { placeholder: s_('Branches|Filter by branch name'), id: 'branch-search', class: 'form-control search-text-input input-short', spellcheck: false } - - unless @mode == 'overview' - .dropdown.inline> - %button.dropdown-menu-toggle{ type: 'button', 'data-toggle' => 'dropdown' } - %span.light - = branches_sort_options_hash[@sort] - = icon('chevron-down') - %ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable - %li.dropdown-header - = s_('Branches|Sort by') - - branches_sort_options_hash.each do |value, title| - %li - = link_to title, project_branches_filtered_path(@project, state: 'all', search: params[:search], sort: value), class: ("is-active" if @sort == value) + - unless @mode == 'overview' + .dropdown.inline> + %button.dropdown-menu-toggle{ type: 'button', 'data-toggle' => 'dropdown' } + %span.light + = branches_sort_options_hash[@sort] + = icon('chevron-down') + %ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable + %li.dropdown-header + = s_('Branches|Sort by') + - branches_sort_options_hash.each do |value, title| + %li + = link_to title, project_branches_filtered_path(@project, state: 'all', search: params[:search], sort: value), class: ("is-active" if @sort == value) - - if can? current_user, :push_code, @project - = link_to project_merged_branches_path(@project), - class: 'btn btn-inverted btn-remove has-tooltip qa-delete-merged-branches', - title: s_("Branches|Delete all branches that are merged into '%{default_branch}'") % { default_branch: @project.repository.root_ref }, - method: :delete, - data: { confirm: s_('Branches|Deleting the merged branches cannot be undone. Are you sure?'), - container: 'body' } do - = s_('Branches|Delete merged branches') - = link_to new_project_branch_path(@project), class: 'btn btn-success' do - = s_('Branches|New branch') + - if can? current_user, :push_code, @project + = link_to project_merged_branches_path(@project), + class: 'btn btn-inverted btn-remove has-tooltip qa-delete-merged-branches', + title: s_("Branches|Delete all branches that are merged into '%{default_branch}'") % { default_branch: @project.repository.root_ref }, + method: :delete, + data: { confirm: s_('Branches|Deleting the merged branches cannot be undone. Are you sure?'), + container: 'body' } do + = s_('Branches|Delete merged branches') + = link_to new_project_branch_path(@project), class: 'btn btn-success' do + = s_('Branches|New branch') - = render_if_exists 'projects/commits/mirror_status' += render_if_exists 'projects/commits/mirror_status' - .js-branch-list{ data: { diverging_counts_endpoint: diverging_commit_counts_namespace_project_branches_path(@project.namespace, @project, format: :json) } } - - if can?(current_user, :admin_project, @project) - - project_settings_link = link_to s_('Branches|project settings'), project_protected_branches_path(@project) - .row-content-block - %h5 - = s_('Branches|Protected branches can be managed in %{project_settings_link}.').html_safe % { project_settings_link: project_settings_link } +.js-branch-list{ data: { diverging_counts_endpoint: diverging_commit_counts_namespace_project_branches_path(@project.namespace, @project, format: :json) } } +- if can?(current_user, :admin_project, @project) + - project_settings_link = link_to s_('Branches|project settings'), project_protected_branches_path(@project) + .row-content-block + %h5 + = s_('Branches|Protected branches can be managed in %{project_settings_link}.').html_safe % { project_settings_link: project_settings_link } - - if @mode == 'overview' && (@active_branches.any? || @stale_branches.any?) - = render "projects/branches/panel", branches: @active_branches, state: 'active', panel_title: s_('Branches|Active branches'), show_more_text: s_('Branches|Show more active branches'), project: @project, overview_max_branches: @overview_max_branches - = render "projects/branches/panel", branches: @stale_branches, state: 'stale', panel_title: s_('Branches|Stale branches'), show_more_text: s_('Branches|Show more stale branches'), project: @project, overview_max_branches: @overview_max_branches +- if @mode == 'overview' && (@active_branches.any? || @stale_branches.any?) + = render "projects/branches/panel", branches: @active_branches, state: 'active', panel_title: s_('Branches|Active branches'), show_more_text: s_('Branches|Show more active branches'), project: @project, overview_max_branches: @overview_max_branches + = render "projects/branches/panel", branches: @stale_branches, state: 'stale', panel_title: s_('Branches|Stale branches'), show_more_text: s_('Branches|Show more stale branches'), project: @project, overview_max_branches: @overview_max_branches - - elsif @branches.any? - %ul.content-list.all-branches - - @branches.each do |branch| - = render "projects/branches/branch", branch: branch, merged: @merged_branch_names.include?(branch.name) - = paginate @branches, theme: 'gitlab' - - else - .nothing-here-block - = s_('Branches|No branches to show') +- elsif @branches.any? + %ul.content-list.all-branches + - @branches.each do |branch| + = render "projects/branches/branch", branch: branch, merged: @merged_branch_names.include?(branch.name) + = paginate @branches, theme: 'gitlab' +- else + .nothing-here-block + = s_('Branches|No branches to show') = render 'projects/branches/delete_protected_modal' diff --git a/app/views/projects/commit/show.html.haml b/app/views/projects/commit/show.html.haml index 34226167288..40b96ca477e 100644 --- a/app/views/projects/commit/show.html.haml +++ b/app/views/projects/commit/show.html.haml @@ -1,3 +1,4 @@ +-# no_container is needed here because of full width side-by-side diff view - @no_container = true - add_to_breadcrumbs _('Commits'), project_commits_path(@project) - breadcrumb_title @commit.short_id diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml index 2db1efdd52f..e155e3758fb 100644 --- a/app/views/projects/commits/show.html.haml +++ b/app/views/projects/commits/show.html.haml @@ -1,4 +1,3 @@ -- @no_container = true - breadcrumb_title _("Commits") - page_title _("Commits"), @ref @@ -6,33 +5,32 @@ = auto_discovery_link_tag(:atom, project_commits_path(@project, @ref, rss_url_options), title: "#{@project.name}:#{@ref} commits") .js-project-commits-show{ 'data-commits-limit' => @limit } - %div{ class: container_class } - .tree-holder - .nav-block - .tree-ref-container - .tree-ref-holder - = render 'shared/ref_switcher', destination: 'commits' - - %ul.breadcrumb.repo-breadcrumb - = commits_breadcrumbs - .tree-controls.d-none.d-sm-none.d-md-block - - if @merge_request.present? - .control - = link_to _("View open merge request"), project_merge_request_path(@project, @merge_request), class: 'btn' - - elsif create_mr_button?(@repository.root_ref, @ref) - .control - = link_to _("Create merge request"), create_mr_path(@repository.root_ref, @ref), class: 'btn btn-success' + .tree-holder + .nav-block + .tree-ref-container + .tree-ref-holder + = render 'shared/ref_switcher', destination: 'commits' + %ul.breadcrumb.repo-breadcrumb + = commits_breadcrumbs + .tree-controls.d-none.d-sm-none.d-md-block + - if @merge_request.present? .control - = form_tag(project_commits_path(@project, @id), method: :get, class: 'commits-search-form js-signature-container', data: { 'signatures-path' => namespace_project_signatures_path }) do - = search_field_tag :search, params[:search], { placeholder: _('Filter by commit message'), id: 'commits-search', class: 'form-control search-text-input input-short', spellcheck: false } + = link_to _("View open merge request"), project_merge_request_path(@project, @merge_request), class: 'btn' + - elsif create_mr_button?(@repository.root_ref, @ref) .control - = link_to project_commits_path(@project, @ref, rss_url_options), title: _("Commits feed"), class: 'btn' do - = icon("rss") + = link_to _("Create merge request"), create_mr_path(@repository.root_ref, @ref), class: 'btn btn-success' + + .control + = form_tag(project_commits_path(@project, @id), method: :get, class: 'commits-search-form js-signature-container', data: { 'signatures-path' => namespace_project_signatures_path }) do + = search_field_tag :search, params[:search], { placeholder: _('Filter by commit message'), id: 'commits-search', class: 'form-control search-text-input input-short', spellcheck: false } + .control + = link_to project_commits_path(@project, @ref, rss_url_options), title: _("Commits feed"), class: 'btn' do + = icon("rss") - = render_if_exists 'projects/commits/mirror_status' + = render_if_exists 'projects/commits/mirror_status' - %div{ id: dom_id(@project) } - %ol#commits-list.list-unstyled.content_list - = render 'commits', project: @project, ref: @ref - = spinner + %div{ id: dom_id(@project) } + %ol#commits-list.list-unstyled.content_list + = render 'commits', project: @project, ref: @ref + = spinner diff --git a/app/views/projects/compare/index.html.haml b/app/views/projects/compare/index.html.haml index 14c64b3534a..02f2b104ce3 100644 --- a/app/views/projects/compare/index.html.haml +++ b/app/views/projects/compare/index.html.haml @@ -1,18 +1,16 @@ -- @no_container = true - breadcrumb_title "Compare Revisions" - page_title "Compare" -%div{ class: container_class } - %h3.page-title - = _("Compare Git revisions") - .sub-header-block - - example_master = capture do - %code.ref-name master - - example_sha = capture do - %code.ref-name 4eedf23 - = (_("Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request.") % { master: example_master, sha: example_sha }).html_safe - %br - = (_("Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision.")).html_safe +%h3.page-title + = _("Compare Git revisions") +.sub-header-block + - example_master = capture do + %code.ref-name master + - example_sha = capture do + %code.ref-name 4eedf23 + = (_("Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request.") % { master: example_master, sha: example_sha }).html_safe + %br + = (_("Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision.")).html_safe - .prepend-top-20 - = render "form" +.prepend-top-20 + = render "form" diff --git a/app/views/projects/compare/show.html.haml b/app/views/projects/compare/show.html.haml index 5774b48a054..51cf95dc84b 100644 --- a/app/views/projects/compare/show.html.haml +++ b/app/views/projects/compare/show.html.haml @@ -1,25 +1,23 @@ -- @no_container = true - add_to_breadcrumbs _("Compare Revisions"), project_compare_index_path(@project) - page_title "#{params[:from]}...#{params[:to]}" -%div{ class: container_class } - .sub-header-block.no-bottom-space - = render "form" +.sub-header-block.no-bottom-space + = render "form" - - if @commits.present? - = render "projects/commits/commit_list" - = render "projects/diffs/diffs", diffs: @diffs, environment: @environment, diff_page_context: "is-compare" - - else - .card.bg-light - .center - %h4 - = s_("CompareBranches|There isn't anything to compare.") - %p.slead - - if params[:to] == params[:from] - - source_branch = capture do - %span.ref-name= params[:from] - - target_branch = capture do - %span.ref-name= params[:to] - = (s_("CompareBranches|%{source_branch} and %{target_branch} are the same.") % { source_branch: source_branch, target_branch: target_branch }).html_safe - - else - = _("You'll need to use different branch names to get a valid comparison.") +- if @commits.present? + = render "projects/commits/commit_list" + = render "projects/diffs/diffs", diffs: @diffs, environment: @environment, diff_page_context: "is-compare" +- else + .card.bg-light + .center + %h4 + = s_("CompareBranches|There isn't anything to compare.") + %p.slead + - if params[:to] == params[:from] + - source_branch = capture do + %span.ref-name= params[:from] + - target_branch = capture do + %span.ref-name= params[:to] + = (s_("CompareBranches|%{source_branch} and %{target_branch} are the same.") % { source_branch: source_branch, target_branch: target_branch }).html_safe + - else + = _("You'll need to use different branch names to get a valid comparison.") diff --git a/app/views/projects/cycle_analytics/show.html.haml b/app/views/projects/cycle_analytics/show.html.haml index 2b594c125f4..6b56a4ee7ab 100644 --- a/app/views/projects/cycle_analytics/show.html.haml +++ b/app/views/projects/cycle_analytics/show.html.haml @@ -1,7 +1,6 @@ -- @no_container = true - page_title "Cycle Analytics" -#cycle-analytics{ class: container_class, "v-cloak" => "true", data: { request_path: project_cycle_analytics_path(@project) } } +#cycle-analytics{ "v-cloak" => "true", data: { request_path: project_cycle_analytics_path(@project) } } - if @cycle_analytics_no_data %banner{ "v-if" => "!isOverviewDialogDismissed", "documentation-link": help_page_path('user/project/cycle_analytics'), diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml index 9fa31c147eb..a9b6b397968 100644 --- a/app/views/projects/empty.html.haml +++ b/app/views/projects/empty.html.haml @@ -1,10 +1,9 @@ - @content_class = "limit-container-width" unless fluid_layout -- @no_container = true - breadcrumb_title _("Details") = render partial: 'flash_messages', locals: { project: @project } -%div{ class: [container_class, ("limit-container-width" unless fluid_layout)] } +%div{ class: [("limit-container-width" unless fluid_layout)] } = render "home_panel" %h4.prepend-top-0.append-bottom-8 diff --git a/app/views/projects/environments/edit.html.haml b/app/views/projects/environments/edit.html.haml index d581bd3aeab..56af252d785 100644 --- a/app/views/projects/environments/edit.html.haml +++ b/app/views/projects/environments/edit.html.haml @@ -1,8 +1,6 @@ -- @no_container = true - page_title _("Edit"), @environment.name, _("Environments") -%div{ class: container_class } - %h3.page-title - = _('Edit environment') - %hr - = render 'form' +%h3.page-title + = _('Edit environment') +%hr += render 'form' diff --git a/app/views/projects/environments/folder.html.haml b/app/views/projects/environments/folder.html.haml index aebd176af9b..f85c57d9aa1 100644 --- a/app/views/projects/environments/folder.html.haml +++ b/app/views/projects/environments/folder.html.haml @@ -1,5 +1,3 @@ -- @no_container = true - page_title _("Environments") -#environments-folder-list-view{ data: { environments_data: environments_folder_list_view_data, - "css-class" => container_class } } +#environments-folder-list-view{ data: { environments_data: environments_folder_list_view_data } } diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml index 3ec92676cde..2ba88da3375 100644 --- a/app/views/projects/environments/index.html.haml +++ b/app/views/projects/environments/index.html.haml @@ -1,4 +1,3 @@ -- @no_container = true - page_title _("Environments") #environments-list-view{ data: { environments_data: environments_list_data, @@ -6,5 +5,4 @@ "can-create-environment" => can?(current_user, :create_environment, @project).to_s, "new-environment-path" => new_project_environment_path(@project), "help-page-path" => help_page_path("ci/environments"), - "deploy-boards-help-path" => help_page_path("user/project/deploy_boards", anchor: "enabling-deploy-boards"), - "css-class" => container_class } } + "deploy-boards-help-path" => help_page_path("user/project/deploy_boards", anchor: "enabling-deploy-boards") } } diff --git a/app/views/projects/environments/metrics.html.haml b/app/views/projects/environments/metrics.html.haml index 7b847a85686..aab30af5ed4 100644 --- a/app/views/projects/environments/metrics.html.haml +++ b/app/views/projects/environments/metrics.html.haml @@ -1,5 +1,4 @@ -- @no_container = true - page_title _("Metrics for environment"), @environment.name -.prometheus-container{ class: container_class } +.prometheus-container #prometheus-graphs{ data: metrics_data(@project, @environment) } diff --git a/app/views/projects/environments/new.html.haml b/app/views/projects/environments/new.html.haml index c1067fdff78..96edd3f0bd7 100644 --- a/app/views/projects/environments/new.html.haml +++ b/app/views/projects/environments/new.html.haml @@ -1,9 +1,7 @@ -- @no_container = true - breadcrumb_title _("Environments") - page_title _("New Environment") -%div{ class: container_class } - %h3.page-title - = _("New environment") - %hr - = render 'form' +%h3.page-title + = _("New environment") +%hr += render 'form' diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml index 6100fd3ad37..75da151f329 100644 --- a/app/views/projects/environments/show.html.haml +++ b/app/views/projects/environments/show.html.haml @@ -1,4 +1,3 @@ -- @no_container = true - add_to_breadcrumbs _("Environments"), project_environments_path(@project) - breadcrumb_title @environment.name - page_title _("Environments") @@ -6,67 +5,66 @@ - content_for :page_specific_javascripts do = stylesheet_link_tag 'page_bundles/xterm' -%div{ class: container_class } - - if can?(current_user, :stop_environment, @environment) - #stop-environment-modal.modal.fade{ tabindex: -1 } - .modal-dialog - .modal-content - .modal-header - %h4.modal-title.d-flex.mw-100 - = s_("Environments|Stopping") - %span.has-tooltip.text-truncate.ml-1.mr-1.flex-fill{ title: @environment.name, data: { container: '#stop-environment-modal' } } - = @environment.name - ? - .modal-body - %p= s_('Environments|Are you sure you want to stop this environment?') - - unless @environment.stop_action_available? - .warning_message - %p= s_('Environments|Note that this action will stop the environment, but it will %{emphasis_start}not%{emphasis_end} have an effect on any existing deployment due to no “stop environment action” being defined in the %{ci_config_link_start}.gitlab-ci.yml%{ci_config_link_end} file.').html_safe % { emphasis_start: '<strong>'.html_safe, - emphasis_end: '</strong>'.html_safe, - ci_config_link_start: '<a href="https://docs.gitlab.com/ee/ci/yaml/" target="_blank" rel="noopener noreferrer">'.html_safe, - ci_config_link_end: '</a>'.html_safe } - %a{ href: 'https://docs.gitlab.com/ee/ci/environments.html#stopping-an-environment', - target: '_blank', - rel: 'noopener noreferrer' } - = s_('Environments|Learn more about stopping environments') - .modal-footer - = button_tag _('Cancel'), type: 'button', class: 'btn btn-cancel', data: { dismiss: 'modal' } - = button_to stop_project_environment_path(@project, @environment), class: 'btn btn-danger has-tooltip', method: :post do - = s_('Environments|Stop environment') +- if can?(current_user, :stop_environment, @environment) + #stop-environment-modal.modal.fade{ tabindex: -1 } + .modal-dialog + .modal-content + .modal-header + %h4.modal-title.d-flex.mw-100 + = s_("Environments|Stopping") + %span.has-tooltip.text-truncate.ml-1.mr-1.flex-fill{ title: @environment.name, data: { container: '#stop-environment-modal' } } + = @environment.name + ? + .modal-body + %p= s_('Environments|Are you sure you want to stop this environment?') + - unless @environment.stop_action_available? + .warning_message + %p= s_('Environments|Note that this action will stop the environment, but it will %{emphasis_start}not%{emphasis_end} have an effect on any existing deployment due to no “stop environment action” being defined in the %{ci_config_link_start}.gitlab-ci.yml%{ci_config_link_end} file.').html_safe % { emphasis_start: '<strong>'.html_safe, + emphasis_end: '</strong>'.html_safe, + ci_config_link_start: '<a href="https://docs.gitlab.com/ee/ci/yaml/" target="_blank" rel="noopener noreferrer">'.html_safe, + ci_config_link_end: '</a>'.html_safe } + %a{ href: 'https://docs.gitlab.com/ee/ci/environments.html#stopping-an-environment', + target: '_blank', + rel: 'noopener noreferrer' } + = s_('Environments|Learn more about stopping environments') + .modal-footer + = button_tag _('Cancel'), type: 'button', class: 'btn btn-cancel', data: { dismiss: 'modal' } + = button_to stop_project_environment_path(@project, @environment), class: 'btn btn-danger has-tooltip', method: :post do + = s_('Environments|Stop environment') - .top-area - %h3.page-title= @environment.name - .nav-controls.ml-auto.my-2 - = render 'projects/environments/terminal_button', environment: @environment - = render 'projects/environments/external_url', environment: @environment - = render 'projects/environments/metrics_button', environment: @environment - - if can?(current_user, :update_environment, @environment) - = link_to _('Edit'), edit_project_environment_path(@project, @environment), class: 'btn' - - if can?(current_user, :stop_environment, @environment) - = button_tag class: 'btn btn-danger', type: 'button', data: { toggle: 'modal', - target: '#stop-environment-modal' } do - = sprite_icon('stop') - = s_('Environments|Stop') +.top-area + %h3.page-title= @environment.name + .nav-controls.ml-auto.my-2 + = render 'projects/environments/terminal_button', environment: @environment + = render 'projects/environments/external_url', environment: @environment + = render 'projects/environments/metrics_button', environment: @environment + - if can?(current_user, :update_environment, @environment) + = link_to _('Edit'), edit_project_environment_path(@project, @environment), class: 'btn' + - if can?(current_user, :stop_environment, @environment) + = button_tag class: 'btn btn-danger', type: 'button', data: { toggle: 'modal', + target: '#stop-environment-modal' } do + = sprite_icon('stop') + = s_('Environments|Stop') - .environments-container - - if @deployments.blank? - .empty-state - .text-content - %h4.state-title - = _("You don't have any deployments right now.") - %p.blank-state-text - = _("Define environments in the deploy stage(s) in <code>.gitlab-ci.yml</code> to track deployments here.").html_safe - .text-center - = link_to _("Read more"), help_page_path("ci/environments"), class: "btn btn-success" - - else - .table-holder - .ci-table.environments{ role: 'grid' } - .gl-responsive-table-row.table-row-header{ role: 'row' } - .table-section.section-10{ role: 'columnheader' }= _('ID') - .table-section.section-30{ role: 'columnheader' }= _('Commit') - .table-section.section-25{ role: 'columnheader' }= _('Job') - .table-section.section-15{ role: 'columnheader' }= _('Created') +.environments-container + - if @deployments.blank? + .empty-state + .text-content + %h4.state-title + = _("You don't have any deployments right now.") + %p.blank-state-text + = _("Define environments in the deploy stage(s) in <code>.gitlab-ci.yml</code> to track deployments here.").html_safe + .text-center + = link_to _("Read more"), help_page_path("ci/environments"), class: "btn btn-success" + - else + .table-holder + .ci-table.environments{ role: 'grid' } + .gl-responsive-table-row.table-row-header{ role: 'row' } + .table-section.section-10{ role: 'columnheader' }= _('ID') + .table-section.section-30{ role: 'columnheader' }= _('Commit') + .table-section.section-25{ role: 'columnheader' }= _('Job') + .table-section.section-15{ role: 'columnheader' }= _('Created') - = render @deployments + = render @deployments - = paginate @deployments, theme: 'gitlab' + = paginate @deployments, theme: 'gitlab' diff --git a/app/views/projects/environments/terminal.html.haml b/app/views/projects/environments/terminal.html.haml index e837d3d56ac..3a705d736f3 100644 --- a/app/views/projects/environments/terminal.html.haml +++ b/app/views/projects/environments/terminal.html.haml @@ -1,23 +1,21 @@ -- @no_container = true - page_title _("Terminal for environment"), @environment.name - content_for :page_specific_javascripts do = stylesheet_link_tag "xterm.css" -%div{ class: container_class } - .top-area - .row - .col-sm-6 - %h3.page-title - = _("Terminal for environment") - = @environment.name +.top-area + .row + .col-sm-6 + %h3.page-title + = _("Terminal for environment") + = @environment.name - .col-sm-6 - .nav-controls - - if @environment.external_url.present? - = link_to @environment.external_url, class: 'btn btn-default', target: '_blank', rel: 'noopener noreferrer nofollow' do - = sprite_icon('external-link') - = render 'projects/deployments/actions', deployment: @environment.last_deployment + .col-sm-6 + .nav-controls + - if @environment.external_url.present? + = link_to @environment.external_url, class: 'btn btn-default', target: '_blank', rel: 'noopener noreferrer nofollow' do + = sprite_icon('external-link') + = render 'projects/deployments/actions', deployment: @environment.last_deployment .terminal-container{ class: container_class } #terminal{ data: { project_path: "#{terminal_project_environment_path(@project, @environment)}.ws" } } diff --git a/app/views/projects/graphs/charts.html.haml b/app/views/projects/graphs/charts.html.haml index 60160f521ad..2a2ccf8a6de 100644 --- a/app/views/projects/graphs/charts.html.haml +++ b/app/views/projects/graphs/charts.html.haml @@ -1,7 +1,6 @@ -- @no_container = true - page_title _("Contribution Charts") -.repo-charts{ class: container_class } +.repo-charts %h4.sub-header = _("Programming languages used in this repository") @@ -20,7 +19,7 @@ .col-md-8 %canvas#languages-chart{ height: 400 } -.repo-charts{ class: container_class } +.repo-charts .sub-header-block.border-top .row.tree-ref-header diff --git a/app/views/projects/graphs/show.html.haml b/app/views/projects/graphs/show.html.haml index 4b2417ff43b..6e5e4607232 100644 --- a/app/views/projects/graphs/show.html.haml +++ b/app/views/projects/graphs/show.html.haml @@ -1,7 +1,6 @@ -- @no_container = true - page_title _('Contributors') -.js-graphs-show{ class: container_class, 'data-project-graph-path': project_graph_path(@project, current_ref, format: :json) } +.js-graphs-show{ 'data-project-graph-path': project_graph_path(@project, current_ref, format: :json) } .sub-header-block .tree-ref-holder.inline.vertical-align-middle = render 'shared/ref_switcher', destination: 'graphs' diff --git a/app/views/projects/imports/show.html.haml b/app/views/projects/imports/show.html.haml index 422a3a22f87..87b027a1802 100644 --- a/app/views/projects/imports/show.html.haml +++ b/app/views/projects/imports/show.html.haml @@ -1,5 +1,4 @@ - page_title import_in_progress_title -- @no_container = true - @content_class = "limit-container-width" unless fluid_layout .save-project-loader diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml index 39e9e9171cf..49e482ff1df 100644 --- a/app/views/projects/issues/index.html.haml +++ b/app/views/projects/issues/index.html.haml @@ -1,4 +1,3 @@ -- @no_container = true - @can_bulk_update = can?(current_user, :admin_issue, @project) - page_title "Issues" @@ -8,18 +7,17 @@ = auto_discovery_link_tag(:atom, safe_params.merge(rss_url_options).to_h, title: "#{@project.name} issues") - if project_issues(@project).exists? - %div{ class: (container_class) } - .top-area - = render 'shared/issuable/nav', type: :issues - = render "projects/issues/nav_btns" - = render 'shared/issuable/search_bar', type: :issues + .top-area + = render 'shared/issuable/nav', type: :issues + = render "projects/issues/nav_btns" + = render 'shared/issuable/search_bar', type: :issues - - if @can_bulk_update - = render 'shared/issuable/bulk_update_sidebar', type: :issues + - if @can_bulk_update + = render 'shared/issuable/bulk_update_sidebar', type: :issues - .issues-holder - = render 'issues' - - if new_issue_email - = render 'projects/issuable_by_email', email: new_issue_email, issuable_type: 'issue' + .issues-holder + = render 'issues' + - if new_issue_email + = render 'projects/issuable_by_email', email: new_issue_email, issuable_type: 'issue' - else = render 'shared/empty_states/issues', button_path: new_project_issue_path(@project), show_import_button: true diff --git a/app/views/projects/jobs/index.html.haml b/app/views/projects/jobs/index.html.haml index afea5268006..5acb2af08e4 100644 --- a/app/views/projects/jobs/index.html.haml +++ b/app/views/projects/jobs/index.html.haml @@ -1,18 +1,16 @@ -- @no_container = true - page_title "Jobs" -%div{ class: container_class } - .top-area - - build_path_proc = ->(scope) { project_jobs_path(@project, scope: scope) } - = render "shared/builds/tabs", build_path_proc: build_path_proc, all_builds: @all_builds, scope: @scope +.top-area + - build_path_proc = ->(scope) { project_jobs_path(@project, scope: scope) } + = render "shared/builds/tabs", build_path_proc: build_path_proc, all_builds: @all_builds, scope: @scope - .nav-controls - - if can?(current_user, :update_build, @project) - - unless @repository.gitlab_ci_yml - = link_to 'Get started with Pipelines', help_page_path('ci/quick_start/README'), class: 'btn btn-info' + .nav-controls + - if can?(current_user, :update_build, @project) + - unless @repository.gitlab_ci_yml + = link_to 'Get started with Pipelines', help_page_path('ci/quick_start/README'), class: 'btn btn-info' - = link_to project_ci_lint_path(@project), class: 'btn btn-default' do - %span CI lint + = link_to project_ci_lint_path(@project), class: 'btn btn-default' do + %span CI lint - .content-list.builds-content-list - = render "table", builds: @builds, project: @project +.content-list.builds-content-list + = render "table", builds: @builds, project: @project diff --git a/app/views/projects/jobs/show.html.haml b/app/views/projects/jobs/show.html.haml index a3688c17041..6bb27a65142 100644 --- a/app/views/projects/jobs/show.html.haml +++ b/app/views/projects/jobs/show.html.haml @@ -1,4 +1,3 @@ -- @no_container = true - add_to_breadcrumbs _("Jobs"), project_jobs_path(@project) - breadcrumb_title "##{@build.id}" - page_title "#{@build.name} (##{@build.id})", _("Jobs") @@ -6,11 +5,10 @@ - content_for :page_specific_javascripts do = stylesheet_link_tag 'page_bundles/xterm' -%div{ class: container_class } - #js-job-vue-app{ data: { endpoint: project_job_path(@project, @build, format: :json), project_path: @project.full_path, - deployment_help_url: help_page_path('user/project/clusters/index.html', anchor: 'troubleshooting-failed-deployment-jobs'), - runner_help_url: help_page_path('ci/runners/README.html', anchor: 'setting-maximum-job-timeout-for-a-runner'), - runner_settings_url: project_runners_path(@build.project, anchor: 'js-runners-settings'), - variables_settings_url: project_variables_path(@build.project, anchor: 'js-cicd-variables-settings'), - page_path: project_job_path(@project, @build), build_status: @build.status, build_stage: @build.stage, log_state: '', - build_options: javascript_build_options } } +#js-job-vue-app{ data: { endpoint: project_job_path(@project, @build, format: :json), project_path: @project.full_path, + deployment_help_url: help_page_path('user/project/clusters/index.html', anchor: 'troubleshooting-failed-deployment-jobs'), + runner_help_url: help_page_path('ci/runners/README.html', anchor: 'setting-maximum-job-timeout-for-a-runner'), + runner_settings_url: project_runners_path(@build.project, anchor: 'js-runners-settings'), + variables_settings_url: project_variables_path(@build.project, anchor: 'js-cicd-variables-settings'), + page_path: project_job_path(@project, @build), build_status: @build.status, build_stage: @build.stage, log_state: '', + build_options: javascript_build_options } } diff --git a/app/views/projects/jobs/terminal.html.haml b/app/views/projects/jobs/terminal.html.haml index f7e7535ee92..5439a4b5d5c 100644 --- a/app/views/projects/jobs/terminal.html.haml +++ b/app/views/projects/jobs/terminal.html.haml @@ -1,4 +1,3 @@ -- @no_container = true - add_to_breadcrumbs 'Jobs', project_jobs_path(@project) - add_to_breadcrumbs "##{@build.id}", project_job_path(@project, @build) - breadcrumb_title 'Terminal' @@ -7,5 +6,5 @@ - content_for :page_specific_javascripts do = stylesheet_link_tag "xterm.css" -.terminal-container{ class: container_class } +.terminal-container #terminal{ data: { project_path: terminal_project_job_path(@project, @build, format: :ws) } } diff --git a/app/views/projects/labels/edit.html.haml b/app/views/projects/labels/edit.html.haml index b9d45e83032..b7996f0dad1 100644 --- a/app/views/projects/labels/edit.html.haml +++ b/app/views/projects/labels/edit.html.haml @@ -1,10 +1,8 @@ -- @no_container = true - add_to_breadcrumbs "Labels", project_labels_path(@project) - breadcrumb_title "Edit" - page_title "Edit", @label.name, "Labels" -%div{ class: container_class } - %h3.page-title - Edit Label - %hr - = render 'shared/labels/form', url: project_label_path(@project, @label), back_path: project_labels_path(@project) +%h3.page-title + Edit Label +%hr += render 'shared/labels/form', url: project_label_path(@project, @label), back_path: project_labels_path(@project) diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml index 511d7a82d1b..0328751c68c 100644 --- a/app/views/projects/labels/index.html.haml +++ b/app/views/projects/labels/index.html.haml @@ -1,4 +1,3 @@ -- @no_container = true - page_title "Labels" - can_admin_label = can?(current_user, :admin_label, @project) - search = params[:search] @@ -7,48 +6,47 @@ - if labels_or_filters #promote-label-modal - %div{ class: container_class } - = render 'shared/labels/nav', labels_or_filters: labels_or_filters, can_admin_label: can_admin_label + = render 'shared/labels/nav', labels_or_filters: labels_or_filters, can_admin_label: can_admin_label - .labels-container.prepend-top-10 - - if can_admin_label && search.blank? - %p.text-muted - = _('Labels can be applied to issues and merge requests.') - %br - = _('Star a label to make it a priority label. Order the prioritized labels to change their relative priority, by dragging.') + .labels-container.prepend-top-10 + - if can_admin_label && search.blank? + %p.text-muted + = _('Labels can be applied to issues and merge requests.') + %br + = _('Star a label to make it a priority label. Order the prioritized labels to change their relative priority, by dragging.') - -# Only show it in the first page - - hide = @available_labels.empty? || (params[:page].present? && params[:page] != '1') - .prioritized-labels{ class: [('hide' if hide), ('is-not-draggable' unless can_admin_label)] } - %h5.prepend-top-10= _('Prioritized Labels') - .content-list.manage-labels-list.js-prioritized-labels{ data: { url: set_priorities_project_labels_path(@project), sortable: can_admin_label } } - #js-priority-labels-empty-state.priority-labels-empty-state{ class: "#{'hidden' unless @prioritized_labels.empty? && search.blank?}" } - = render 'shared/empty_states/priority_labels' - - if @prioritized_labels.present? - = render partial: 'shared/label', collection: @prioritized_labels, as: :label, locals: { force_priority: true, subject: @project } - - elsif search.present? - .nothing-here-block - = _('No prioritised labels with such name or description') + -# Only show it in the first page + - hide = @available_labels.empty? || (params[:page].present? && params[:page] != '1') + .prioritized-labels{ class: [('hide' if hide), ('is-not-draggable' unless can_admin_label)] } + %h5.prepend-top-10= _('Prioritized Labels') + .content-list.manage-labels-list.js-prioritized-labels{ data: { url: set_priorities_project_labels_path(@project), sortable: can_admin_label } } + #js-priority-labels-empty-state.priority-labels-empty-state{ class: "#{'hidden' unless @prioritized_labels.empty? && search.blank?}" } + = render 'shared/empty_states/priority_labels' + - if @prioritized_labels.present? + = render partial: 'shared/label', collection: @prioritized_labels, as: :label, locals: { force_priority: true, subject: @project } + - elsif search.present? + .nothing-here-block + = _('No prioritised labels with such name or description') - - if @labels.present? - .other-labels - %h5{ class: ('hide' if hide) }= _('Other Labels') - .content-list.manage-labels-list.js-other-labels - = render partial: 'shared/label', collection: @labels, as: :label, locals: { subject: @project } - = paginate @labels, theme: 'gitlab' - - elsif search.present? - .other-labels - - if @available_labels.any? - %h5 - = _('Other Labels') - .nothing-here-block - = _('No other labels with such name or description') - - else - .nothing-here-block - = _('No labels with such name or description') - - elsif subscribed.present? - .nothing-here-block - = _('You do not have any subscriptions yet') + - if @labels.present? + .other-labels + %h5{ class: ('hide' if hide) }= _('Other Labels') + .content-list.manage-labels-list.js-other-labels + = render partial: 'shared/label', collection: @labels, as: :label, locals: { subject: @project } + = paginate @labels, theme: 'gitlab' + - elsif search.present? + .other-labels + - if @available_labels.any? + %h5 + = _('Other Labels') + .nothing-here-block + = _('No other labels with such name or description') + - else + .nothing-here-block + = _('No labels with such name or description') + - elsif subscribed.present? + .nothing-here-block + = _('You do not have any subscriptions yet') - else = render 'shared/empty_states/labels' diff --git a/app/views/projects/labels/new.html.haml b/app/views/projects/labels/new.html.haml index c6739231e36..96ce0eba2c6 100644 --- a/app/views/projects/labels/new.html.haml +++ b/app/views/projects/labels/new.html.haml @@ -1,10 +1,8 @@ -- @no_container = true - add_to_breadcrumbs "Labels", project_labels_path(@project) - breadcrumb_title "New" - page_title "New Label" -%div{ class: container_class } - %h3.page-title - New Label - %hr - = render 'shared/labels/form', url: project_labels_path(@project), back_path: project_labels_path(@project) +%h3.page-title + New Label +%hr += render 'shared/labels/form', url: project_labels_path(@project), back_path: project_labels_path(@project) diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml index 623380c9c61..4e30f09b9a2 100644 --- a/app/views/projects/merge_requests/index.html.haml +++ b/app/views/projects/merge_requests/index.html.haml @@ -1,4 +1,3 @@ -- @no_container = true - @can_bulk_update = can?(current_user, :admin_merge_request, @project) - merge_project = merge_request_source_project_for_project(@project) - new_merge_request_path = project_new_merge_request_path(merge_project) if merge_project @@ -6,24 +5,22 @@ - page_title "Merge Requests" - new_merge_request_email = @project.new_issuable_address(current_user, 'merge_request') -%div{ class: container_class } - = render 'projects/last_push' += render 'projects/last_push' - if @project.merge_requests.exists? - %div{ class: container_class } - .top-area - = render 'shared/issuable/nav', type: :merge_requests - .nav-controls - = render "projects/merge_requests/nav_btns", merge_project: merge_project, new_merge_request_path: new_merge_request_path + .top-area + = render 'shared/issuable/nav', type: :merge_requests + .nav-controls + = render "projects/merge_requests/nav_btns", merge_project: merge_project, new_merge_request_path: new_merge_request_path - = render 'shared/issuable/search_bar', type: :merge_requests + = render 'shared/issuable/search_bar', type: :merge_requests - - if @can_bulk_update - = render 'shared/issuable/bulk_update_sidebar', type: :merge_requests + - if @can_bulk_update + = render 'shared/issuable/bulk_update_sidebar', type: :merge_requests - .merge-requests-holder - = render 'merge_requests' - - if new_merge_request_email - = render 'projects/issuable_by_email', email: new_merge_request_email, issuable_type: 'merge_request' + .merge-requests-holder + = render 'merge_requests' + - if new_merge_request_email + = render 'projects/issuable_by_email', email: new_merge_request_email, issuable_type: 'merge_request' - else = render 'shared/empty_states/merge_requests', button_path: new_merge_request_path diff --git a/app/views/projects/milestones/_deprecation_message.html.haml b/app/views/projects/milestones/_deprecation_message.html.haml deleted file mode 100644 index b2cca3690d6..00000000000 --- a/app/views/projects/milestones/_deprecation_message.html.haml +++ /dev/null @@ -1,7 +0,0 @@ -.banner-callout.compact.milestone-deprecation-message.prepend-top-20 - .banner-graphic= image_tag 'illustrations/milestone_removing-page.svg' - .banner-body.prepend-left-10.append-right-10 - %h5.banner-title.prepend-top-0 - = _('The tabs below will be removed in a future version') - %p.milestone-banner-text - = _('Learn more about %{issue_boards_url}, to keep track of issues in multiple lists, using labels, assignees, and milestones. If you’re missing something from issue boards, please create an issue on %{gitlab_issues_url}.').html_safe % { issue_boards_url: link_to(_('issue boards'), help_page_url('user/project/issue_board'), target: '_blank', rel: 'noopener noreferrer'), gitlab_issues_url: link_to(_('GitLab’s issue tracker'), 'https://gitlab.com/gitlab-org/gitlab-ce/issues', target: '_blank', rel: 'noopener noreferrer') } diff --git a/app/views/projects/milestones/edit.html.haml b/app/views/projects/milestones/edit.html.haml index aa564e00af9..0d040a5cdb3 100644 --- a/app/views/projects/milestones/edit.html.haml +++ b/app/views/projects/milestones/edit.html.haml @@ -1,14 +1,10 @@ -- @no_container = true - breadcrumb_title _('Edit') - add_to_breadcrumbs _('Milestones'), project_milestones_path(@project) - page_title _('Edit'), @milestone.title, _('Milestones') +%h3.page-title + = _('Edit Milestone') -%div{ class: container_class } +%hr - %h3.page-title - = _('Edit Milestone') - - %hr - - = render 'form' += render 'form' diff --git a/app/views/projects/milestones/index.html.haml b/app/views/projects/milestones/index.html.haml index a3414c16d73..c89566dac90 100644 --- a/app/views/projects/milestones/index.html.haml +++ b/app/views/projects/milestones/index.html.haml @@ -1,26 +1,24 @@ -- @no_container = true - page_title _('Milestones') -%div{ class: container_class } - .top-area - = render 'shared/milestones_filter', counts: milestone_counts(@project.milestones) +.top-area + = render 'shared/milestones_filter', counts: milestone_counts(@project.milestones) - .nav-controls - = render 'shared/milestones/search_form' - = render 'shared/milestones_sort_dropdown' - - if can?(current_user, :admin_milestone, @project) - = link_to new_project_milestone_path(@project), class: 'btn btn-success qa-new-project-milestone', title: _('New milestone') do - = _('New milestone') + .nav-controls + = render 'shared/milestones/search_form' + = render 'shared/milestones_sort_dropdown' + - if can?(current_user, :admin_milestone, @project) + = link_to new_project_milestone_path(@project), class: 'btn btn-success qa-new-project-milestone', title: _('New milestone') do + = _('New milestone') - .milestones - #delete-milestone-modal - #promote-milestone-modal +.milestones + #delete-milestone-modal + #promote-milestone-modal - %ul.content-list - = render @milestones + %ul.content-list + = render @milestones - - if @milestones.blank? - %li - .nothing-here-block= _('No milestones to show') + - if @milestones.blank? + %li + .nothing-here-block= _('No milestones to show') - = paginate @milestones, theme: 'gitlab' + = paginate @milestones, theme: 'gitlab' diff --git a/app/views/projects/milestones/new.html.haml b/app/views/projects/milestones/new.html.haml index 79207fd70b5..721506a2201 100644 --- a/app/views/projects/milestones/new.html.haml +++ b/app/views/projects/milestones/new.html.haml @@ -1,12 +1,10 @@ -- @no_container = true - add_to_breadcrumbs _('Milestones'), project_milestones_path(@project) - breadcrumb_title _('New') - page_title _('New Milestone') -%div{ class: container_class } - %h3.page-title - = _('New Milestone') +%h3.page-title + = _('New Milestone') - %hr +%hr - = render 'form' += render 'form' diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml index 1cee8be604a..49d3039d0c9 100644 --- a/app/views/projects/milestones/show.html.haml +++ b/app/views/projects/milestones/show.html.haml @@ -1,71 +1,68 @@ -- @no_container = true - add_to_breadcrumbs _('Milestones'), project_milestones_path(@project) - breadcrumb_title @milestone.title - page_title @milestone.title, _('Milestones') - page_description @milestone.description -%div{ class: container_class } - .detail-page-header.milestone-page-header - .status-box{ class: status_box_class(@milestone) } - - if @milestone.closed? - = _('Closed') - - elsif @milestone.expired? - = _('Past due') - - elsif @milestone.upcoming? - = _('Upcoming') - - else - = _('Open') - .header-text-content - %span.identifier - %strong - = _('Milestone') - - if @milestone.due_date || @milestone.start_date - = milestone_date_range(@milestone) - .milestone-buttons - - if can?(current_user, :admin_milestone, @project) - = link_to edit_project_milestone_path(@project, @milestone), class: 'btn btn-grouped btn-nr' do - = _('Edit') +.detail-page-header.milestone-page-header + .status-box{ class: status_box_class(@milestone) } + - if @milestone.closed? + = _('Closed') + - elsif @milestone.expired? + = _('Past due') + - elsif @milestone.upcoming? + = _('Upcoming') + - else + = _('Open') + .header-text-content + %span.identifier + %strong + = _('Milestone') + - if @milestone.due_date || @milestone.start_date + = milestone_date_range(@milestone) + .milestone-buttons + - if can?(current_user, :admin_milestone, @project) + = link_to edit_project_milestone_path(@project, @milestone), class: 'btn btn-grouped btn-nr' do + = _('Edit') - - if @project.group - %button.js-promote-project-milestone-button.btn.btn-grouped{ data: { toggle: 'modal', - target: '#promote-milestone-modal', - milestone_title: @milestone.title, - group_name: @project.group.name, - url: promote_project_milestone_path(@milestone.project, @milestone), - container: 'body' }, - disabled: true, - type: 'button' } - = _('Promote') - #promote-milestone-modal + - if @project.group + %button.js-promote-project-milestone-button.btn.btn-grouped{ data: { toggle: 'modal', + target: '#promote-milestone-modal', + milestone_title: @milestone.title, + group_name: @project.group.name, + url: promote_project_milestone_path(@milestone.project, @milestone), + container: 'body' }, + disabled: true, + type: 'button' } + = _('Promote') + #promote-milestone-modal - - if @milestone.active? - = link_to _('Close milestone'), project_milestone_path(@project, @milestone, milestone: {state_event: :close }), method: :put, class: 'btn btn-close btn-nr btn-grouped' - - else - = link_to _('Reopen milestone'), project_milestone_path(@project, @milestone, milestone: {state_event: :activate }), method: :put, class: 'btn btn-reopen btn-nr btn-grouped' + - if @milestone.active? + = link_to _('Close milestone'), project_milestone_path(@project, @milestone, milestone: {state_event: :close }), method: :put, class: 'btn btn-close btn-nr btn-grouped' + - else + = link_to _('Reopen milestone'), project_milestone_path(@project, @milestone, milestone: {state_event: :activate }), method: :put, class: 'btn btn-reopen btn-nr btn-grouped' - = render 'shared/milestones/delete_button' + = render 'shared/milestones/delete_button' - %a.btn.btn-default.btn-grouped.float-right.d-block.d-sm-none.js-sidebar-toggle{ href: '#' } - = icon('angle-double-left') + %a.btn.btn-default.btn-grouped.float-right.d-block.d-sm-none.js-sidebar-toggle{ href: '#' } + = icon('angle-double-left') - .detail-page-description.milestone-detail - %h2.title.qa-milestone-title - = markdown_field(@milestone, :title) +.detail-page-description.milestone-detail + %h2.title.qa-milestone-title + = markdown_field(@milestone, :title) - %div - - if @milestone.description.present? - .description.md - = markdown_field(@milestone, :description) + %div + - if @milestone.description.present? + .description.md + = markdown_field(@milestone, :description) - = render_if_exists 'shared/milestones/burndown', milestone: @milestone, project: @project += render_if_exists 'shared/milestones/burndown', milestone: @milestone, project: @project - - if can?(current_user, :read_issue, @project) && @milestone.total_issues_count(current_user).zero? - .alert.alert-success.prepend-top-default - %span= _('Assign some issues to this milestone.') - - elsif @milestone.complete?(current_user) && @milestone.active? - .alert.alert-success.prepend-top-default - %span= _('All issues for this milestone are closed. You may close this milestone now.') +- if can?(current_user, :read_issue, @project) && @milestone.total_issues_count(current_user).zero? + .alert.alert-success.prepend-top-default + %span= _('Assign some issues to this milestone.') +- elsif @milestone.complete?(current_user) && @milestone.active? + .alert.alert-success.prepend-top-default + %span= _('All issues for this milestone are closed. You may close this milestone now.') - = render 'deprecation_message' - = render 'shared/milestones/tabs', milestone: @milestone - = render 'shared/milestones/sidebar', milestone: @milestone, project: @project, affix_offset: 153 += render 'shared/milestones/tabs', milestone: @milestone += render 'shared/milestones/sidebar', milestone: @milestone, project: @project, affix_offset: 153 diff --git a/app/views/projects/network/_head.html.haml b/app/views/projects/network/_head.html.haml index f08526f485e..701cb37a1c8 100644 --- a/app/views/projects/network/_head.html.haml +++ b/app/views/projects/network/_head.html.haml @@ -1,9 +1,6 @@ -- @no_container = true +.row-content-block.second-block.content-component-block + .tree-ref-holder + = render partial: 'shared/ref_switcher', locals: {destination: 'graph'} -%div{ class: container_class } - .row-content-block.second-block.content-component-block - .tree-ref-holder - = render partial: 'shared/ref_switcher', locals: {destination: 'graph'} - - .oneline - = _("You can move around the graph by using the arrow keys.") + .oneline + = _("You can move around the graph by using the arrow keys.") diff --git a/app/views/projects/pipeline_schedules/index.html.haml b/app/views/projects/pipeline_schedules/index.html.haml index 0580c15ad15..4a0be9e67cb 100644 --- a/app/views/projects/pipeline_schedules/index.html.haml +++ b/app/views/projects/pipeline_schedules/index.html.haml @@ -1,22 +1,20 @@ - breadcrumb_title _("Schedules") -- @no_container = true - page_title _("Pipeline Schedules") -%div{ class: container_class } - #pipeline-schedules-callout{ data: { docs_url: help_page_path('user/project/pipelines/schedules') } } - .top-area - - schedule_path_proc = ->(scope) { pipeline_schedules_path(@project, scope: scope) } - = render "tabs", schedule_path_proc: schedule_path_proc, all_schedules: @all_schedules, scope: @scope +#pipeline-schedules-callout{ data: { docs_url: help_page_path('user/project/pipelines/schedules') } } +.top-area + - schedule_path_proc = ->(scope) { pipeline_schedules_path(@project, scope: scope) } + = render "tabs", schedule_path_proc: schedule_path_proc, all_schedules: @all_schedules, scope: @scope - - if can?(current_user, :create_pipeline_schedule, @project) - .nav-controls - = link_to new_project_pipeline_schedule_path(@project), class: 'btn btn-success' do - %span= _('New schedule') + - if can?(current_user, :create_pipeline_schedule, @project) + .nav-controls + = link_to new_project_pipeline_schedule_path(@project), class: 'btn btn-success' do + %span= _('New schedule') - - if @schedules.present? - %ul.content-list - = render partial: "table" - - else - .card.bg-light - .nothing-here-block= _("No schedules") +- if @schedules.present? + %ul.content-list + = render partial: "table" +- else + .card.bg-light + .nothing-here-block= _("No schedules") diff --git a/app/views/projects/pipelines/charts.html.haml b/app/views/projects/pipelines/charts.html.haml index 6b4110e07d2..c9a50b97fea 100644 --- a/app/views/projects/pipelines/charts.html.haml +++ b/app/views/projects/pipelines/charts.html.haml @@ -1,10 +1,6 @@ -- @no_container = true - page_title _('CI / CD Charts') -%div{ class: container_class } - - #charts.ci-charts - = render 'projects/pipelines/charts/overall' - - %hr - = render 'projects/pipelines/charts/pipelines' +#charts.ci-charts + = render 'projects/pipelines/charts/overall' + %hr + = render 'projects/pipelines/charts/pipelines' diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml index 4e4638085fd..f64f07487fd 100644 --- a/app/views/projects/pipelines/index.html.haml +++ b/app/views/projects/pipelines/index.html.haml @@ -1,17 +1,15 @@ -- @no_container = true - page_title _('Pipelines') = render_if_exists "shared/shared_runners_minutes_limit_flash_message" -%div{ 'class' => container_class } - #pipelines-list-vue{ data: { endpoint: project_pipelines_path(@project, format: :json), - "help-page-path" => help_page_path('ci/quick_start/README'), - "help-auto-devops-path" => help_page_path('topics/autodevops/index.md'), - "empty-state-svg-path" => image_path('illustrations/pipelines_empty.svg'), - "error-state-svg-path" => image_path('illustrations/pipelines_failed.svg'), - "no-pipelines-svg-path" => image_path('illustrations/pipelines_pending.svg'), - "can-create-pipeline" => can?(current_user, :create_pipeline, @project).to_s, - "new-pipeline-path" => can?(current_user, :create_pipeline, @project) && new_project_pipeline_path(@project), - "ci-lint-path" => can?(current_user, :create_pipeline, @project) && project_ci_lint_path(@project), - "reset-cache-path" => can?(current_user, :admin_pipeline, @project) && reset_cache_project_settings_ci_cd_path(@project) , - "has-gitlab-ci" => (@project.has_ci? && @project.builds_enabled?).to_s } } +#pipelines-list-vue{ data: { endpoint: project_pipelines_path(@project, format: :json), + "help-page-path" => help_page_path('ci/quick_start/README'), + "help-auto-devops-path" => help_page_path('topics/autodevops/index.md'), + "empty-state-svg-path" => image_path('illustrations/pipelines_empty.svg'), + "error-state-svg-path" => image_path('illustrations/pipelines_failed.svg'), + "no-pipelines-svg-path" => image_path('illustrations/pipelines_pending.svg'), + "can-create-pipeline" => can?(current_user, :create_pipeline, @project).to_s, + "new-pipeline-path" => can?(current_user, :create_pipeline, @project) && new_project_pipeline_path(@project), + "ci-lint-path" => can?(current_user, :create_pipeline, @project) && project_ci_lint_path(@project), + "reset-cache-path" => can?(current_user, :admin_pipeline, @project) && reset_cache_project_settings_ci_cd_path(@project) , + "has-gitlab-ci" => (@project.has_ci? && @project.builds_enabled?).to_s } } diff --git a/app/views/projects/pipelines/show.html.haml b/app/views/projects/pipelines/show.html.haml index 8a6d7b082e3..2b2133b8296 100644 --- a/app/views/projects/pipelines/show.html.haml +++ b/app/views/projects/pipelines/show.html.haml @@ -1,9 +1,8 @@ -- @no_container = true - add_to_breadcrumbs _('Pipelines'), project_pipelines_path(@project) - breadcrumb_title "##{@pipeline.id}" - page_title _('Pipeline') -.js-pipeline-container{ class: container_class, data: { controller_action: "#{controller.action_name}" } } +.js-pipeline-container{ data: { controller_action: "#{controller.action_name}" } } #js-pipeline-header-vue.pipeline-header-container - if @pipeline.commit.present? diff --git a/app/views/projects/releases/index.html.haml b/app/views/projects/releases/index.html.haml index 28bb4e032eb..326b83c856e 100644 --- a/app/views/projects/releases/index.html.haml +++ b/app/views/projects/releases/index.html.haml @@ -1,5 +1,3 @@ -- @no_container = true - page_title _('Releases') -%div{ class: container_class } - #js-releases-page{ data: { project_id: @project.id, illustration_path: image_path('illustrations/releases.svg'), documentation_path: help_page_path('user/project/releases/index') } } +#js-releases-page{ data: { project_id: @project.id, illustration_path: image_path('illustrations/releases.svg'), documentation_path: help_page_path('user/project/releases/index') } } diff --git a/app/views/projects/serverless/functions/index.html.haml b/app/views/projects/serverless/functions/index.html.haml index bac6c76684b..09f4e556949 100644 --- a/app/views/projects/serverless/functions/index.html.haml +++ b/app/views/projects/serverless/functions/index.html.haml @@ -1,4 +1,3 @@ -- @no_container = true - @content_class = "limit-container-width" unless fluid_layout - breadcrumb_title 'Serverless' - page_title 'Serverless' @@ -10,7 +9,7 @@ clusters_path: clusters_path, help_path: help_page_path('user/project/clusters/serverless/index') } } -%div{ class: [container_class, ('limit-container-width' unless fluid_layout)] } +%div{ class: [('limit-container-width' unless fluid_layout)] } .js-serverless-functions-notice .flash-container diff --git a/app/views/projects/serverless/functions/show.html.haml b/app/views/projects/serverless/functions/show.html.haml index d1fe208ce60..79bb943d6ed 100644 --- a/app/views/projects/serverless/functions/show.html.haml +++ b/app/views/projects/serverless/functions/show.html.haml @@ -1,4 +1,3 @@ -- @no_container = true - @content_class = "limit-container-width" unless fluid_layout - clusters_path = project_clusters_path(@project) - help_path = help_page_path('user/project/clusters/serverless/index') @@ -12,7 +11,7 @@ clusters_path: clusters_path, help_path: help_path } } -%div{ class: [container_class, ('limit-container-width' unless fluid_layout)] } +%div{ class: [('limit-container-width' unless fluid_layout)] } .serverless-function-details#js-serverless-function-details .js-serverless-function-notice diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index c87a084740b..b58af545439 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -1,4 +1,3 @@ -- @no_container = true - breadcrumb_title _("Details") - @content_class = "limit-container-width" unless fluid_layout @@ -11,7 +10,7 @@ - signatures_path = project_signatures_path(@project, @project.default_branch) .js-signature-container{ data: { 'signatures-path': signatures_path } } -%div{ class: [container_class, ("limit-container-width" unless fluid_layout)] } +%div{ class: [("limit-container-width" unless fluid_layout)] } = render "projects/last_push" = render "home_panel" diff --git a/app/views/projects/tags/index.html.haml b/app/views/projects/tags/index.html.haml index 1f0de1e2603..6ad7cf1848f 100644 --- a/app/views/projects/tags/index.html.haml +++ b/app/views/projects/tags/index.html.haml @@ -1,10 +1,9 @@ -- @no_container = true - @sort ||= sort_value_recently_updated - page_title s_('TagsPage|Tags') = content_for :meta_tags do = auto_discovery_link_tag(:atom, project_tags_url(@project, rss_url_options), title: "#{@project.name} tags") -.flex-list{ class: container_class } +.flex-list .top-area.adjust .nav-text.row-main-content = s_('TagsPage|Tags give the ability to mark specific points in history as being important') diff --git a/app/views/projects/tags/releases/edit.html.haml b/app/views/projects/tags/releases/edit.html.haml index e4efeed04f0..40d886ff1af 100644 --- a/app/views/projects/tags/releases/edit.html.haml +++ b/app/views/projects/tags/releases/edit.html.haml @@ -1,22 +1,19 @@ -- @no_container = true - add_to_breadcrumbs "Tags", project_tags_path(@project) - breadcrumb_title @tag.name - page_title "Edit", @tag.name, "Tags" -%div{ class: container_class } - .sub-header-block.no-bottom-space - .oneline - .title - Release notes for tag - %strong= @tag.name +.sub-header-block.no-bottom-space + .oneline + .title + Release notes for tag + %strong= @tag.name - - = form_for(@release, method: :put, url: project_tag_release_path(@project, @tag.name), - html: { class: 'common-note-form release-form js-quick-submit' }) do |f| - = render layout: 'projects/md_preview', locals: { url: preview_markdown_path(@project), referenced_users: true } do - = render 'projects/zen', f: f, attr: :description, classes: 'note-textarea', placeholder: "Write your release notes or drag files here…" - = render 'shared/notes/hints' - .error-alert - .prepend-top-default - = f.submit 'Save changes', class: 'btn btn-success' - = link_to "Cancel", project_tag_path(@project, @tag.name), class: "btn btn-default btn-cancel" += form_for(@release, method: :put, url: project_tag_release_path(@project, @tag.name), + html: { class: 'common-note-form release-form js-quick-submit' }) do |f| + = render layout: 'projects/md_preview', locals: { url: preview_markdown_path(@project), referenced_users: true } do + = render 'projects/zen', f: f, attr: :description, classes: 'note-textarea', placeholder: "Write your release notes or drag files here…" + = render 'shared/notes/hints' + .error-alert + .prepend-top-default + = f.submit 'Save changes', class: 'btn btn-success' + = link_to "Cancel", project_tag_path(@project, @tag.name), class: "btn btn-default btn-cancel" diff --git a/app/views/projects/tags/show.html.haml b/app/views/projects/tags/show.html.haml index 78cce58938e..417cd7a8fee 100644 --- a/app/views/projects/tags/show.html.haml +++ b/app/views/projects/tags/show.html.haml @@ -1,45 +1,43 @@ -- @no_container = true - add_to_breadcrumbs s_('TagsPage|Tags'), project_tags_path(@project) - breadcrumb_title @tag.name - page_title @tag.name, s_('TagsPage|Tags') -%div{ class: container_class } - .top-area.multi-line.flex-wrap - .nav-text - .title - %span.item-title.ref-name - = icon('tag') - = @tag.name - - if protected_tag?(@project, @tag) - %span.badge.badge-success - = s_('TagsPage|protected') - - if @commit - = render 'projects/branches/commit', commit: @commit, project: @project - - else - = s_("TagsPage|Can't find HEAD commit for this tag") +.top-area.multi-line.flex-wrap + .nav-text + .title + %span.item-title.ref-name + = icon('tag') + = @tag.name + - if protected_tag?(@project, @tag) + %span.badge.badge-success + = s_('TagsPage|protected') + - if @commit + = render 'projects/branches/commit', commit: @commit, project: @project + - else + = s_("TagsPage|Can't find HEAD commit for this tag") - .nav-controls - - if can?(current_user, :admin_tag, @project) - = link_to edit_project_tag_release_path(@project, @tag.name), class: 'btn btn-edit controls-item has-tooltip', title: s_('TagsPage|Edit release notes') do - = icon("pencil") - = link_to project_tree_path(@project, @tag.name), class: 'btn controls-item has-tooltip', title: s_('TagsPage|Browse files') do - = sprite_icon('folder-open') - = link_to project_commits_path(@project, @tag.name), class: 'btn controls-item has-tooltip', title: s_('TagsPage|Browse commits') do - = icon('history') - .btn-container.controls-item - = render 'projects/buttons/download', project: @project, ref: @tag.name - - if can?(current_user, :admin_tag, @project) - .btn-container.controls-item-full - = link_to project_tag_path(@project, @tag.name), class: "btn btn-remove remove-row has-tooltip #{protected_tag?(@project, @tag) ? 'disabled' : ''}", title: s_('TagsPage|Delete tag'), method: :delete, data: { confirm: s_('TagsPage|Deleting the %{tag_name} tag cannot be undone. Are you sure?') % { tag_name: @tag.name } } do - %i.fa.fa-trash-o + .nav-controls + - if can?(current_user, :admin_tag, @project) + = link_to edit_project_tag_release_path(@project, @tag.name), class: 'btn btn-edit controls-item has-tooltip', title: s_('TagsPage|Edit release notes') do + = icon("pencil") + = link_to project_tree_path(@project, @tag.name), class: 'btn controls-item has-tooltip', title: s_('TagsPage|Browse files') do + = sprite_icon('folder-open') + = link_to project_commits_path(@project, @tag.name), class: 'btn controls-item has-tooltip', title: s_('TagsPage|Browse commits') do + = icon('history') + .btn-container.controls-item + = render 'projects/buttons/download', project: @project, ref: @tag.name + - if can?(current_user, :admin_tag, @project) + .btn-container.controls-item-full + = link_to project_tag_path(@project, @tag.name), class: "btn btn-remove remove-row has-tooltip #{protected_tag?(@project, @tag) ? 'disabled' : ''}", title: s_('TagsPage|Delete tag'), method: :delete, data: { confirm: s_('TagsPage|Deleting the %{tag_name} tag cannot be undone. Are you sure?') % { tag_name: @tag.name } } do + %i.fa.fa-trash-o - - if @tag.message.present? - %pre.wrap - = strip_gpg_signature(@tag.message) + - if @tag.message.present? + %pre.wrap + = strip_gpg_signature(@tag.message) - .append-bottom-default.prepend-top-default - - if @release.description.present? - .description.md - = markdown_field(@release, :description) - - else - = s_('TagsPage|This tag has no release notes.') +.append-bottom-default.prepend-top-default + - if @release.description.present? + .description.md + = markdown_field(@release, :description) + - else + = s_('TagsPage|This tag has no release notes.') diff --git a/app/views/projects/tree/show.html.haml b/app/views/projects/tree/show.html.haml index 9d2aee7a8bd..39b29a20df6 100644 --- a/app/views/projects/tree/show.html.haml +++ b/app/views/projects/tree/show.html.haml @@ -1,4 +1,3 @@ -- @no_container = true - breadcrumb_title _("Repository") - @content_class = "limit-container-width" unless fluid_layout - signatures_path = namespace_project_signatures_path(namespace_id: @project.namespace.full_path, project_id: @project.path, id: @last_commit) @@ -9,6 +8,5 @@ .js-signature-container{ data: { 'signatures-path': signatures_path } } -%div{ class: [(container_class), ("limit-container-width" unless fluid_layout)] } - = render 'projects/last_push' - = render 'projects/files', commit: @last_commit, project: @project, ref: @ref, content_url: project_tree_path(@project, @id) += render 'projects/last_push' += render 'projects/files', commit: @last_commit, project: @project, ref: @ref, content_url: project_tree_path(@project, @id) diff --git a/app/views/projects/wikis/pages.html.haml b/app/views/projects/wikis/pages.html.haml index 275dc5dbd23..d9dcd8f9acd 100644 --- a/app/views/projects/wikis/pages.html.haml +++ b/app/views/projects/wikis/pages.html.haml @@ -1,34 +1,32 @@ -- @no_container = true - add_to_breadcrumbs "Wiki", project_wiki_path(@project, :home) - breadcrumb_title s_("Wiki|Pages") - page_title s_("Wiki|Pages"), _("Wiki") - sort_title = wiki_sort_title(params[:sort]) -%div{ class: container_class } - .wiki-page-header.top-area.flex-column.flex-lg-row +.wiki-page-header.top-area.flex-column.flex-lg-row - .nav-text.flex-fill - %h2.wiki-page-title - = s_("Wiki|Wiki Pages") + .nav-text.flex-fill + %h2.wiki-page-title + = s_("Wiki|Wiki Pages") - .nav-controls.pb-md-3.pb-lg-0 - = link_to project_wikis_git_access_path(@project), class: 'btn' do - = icon('cloud-download') - = _("Clone repository") + .nav-controls.pb-md-3.pb-lg-0 + = link_to project_wikis_git_access_path(@project), class: 'btn' do + = icon('cloud-download') + = _("Clone repository") - .dropdown.inline.wiki-sort-dropdown + .dropdown.inline.wiki-sort-dropdown + .btn-group{ role: 'group' } .btn-group{ role: 'group' } - .btn-group{ role: 'group' } - %button.dropdown-toggle{ type: 'button', data: { toggle: 'dropdown', display: 'static' }, class: 'btn btn-default' } - = sort_title - = icon('chevron-down') - %ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable.dropdown-menu-sort - %li - = sortable_item(s_("Wiki|Title"), project_wikis_pages_path(@project, sort: ProjectWiki::TITLE_ORDER), sort_title) - = sortable_item(s_("Wiki|Created date"), project_wikis_pages_path(@project, sort: ProjectWiki::CREATED_AT_ORDER), sort_title) - = wiki_sort_controls(@project, params[:sort], params[:direction]) + %button.dropdown-toggle{ type: 'button', data: { toggle: 'dropdown', display: 'static' }, class: 'btn btn-default' } + = sort_title + = icon('chevron-down') + %ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable.dropdown-menu-sort + %li + = sortable_item(s_("Wiki|Title"), project_wikis_pages_path(@project, sort: ProjectWiki::TITLE_ORDER), sort_title) + = sortable_item(s_("Wiki|Created date"), project_wikis_pages_path(@project, sort: ProjectWiki::CREATED_AT_ORDER), sort_title) + = wiki_sort_controls(@project, params[:sort], params[:direction]) - %ul.wiki-pages-list.content-list - = render @wiki_entries, context: 'pages' +%ul.wiki-pages-list.content-list + = render @wiki_entries, context: 'pages' - = paginate @wiki_pages, theme: 'gitlab' += paginate @wiki_pages, theme: 'gitlab' diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index 73bee7c2586..e1c75d5d0f4 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -1,9 +1,9 @@ - @hide_top_links = true - @hide_breadcrumbs = true +- @no_container = true - page_title @user.name - page_description @user.bio - header_title @user.name, user_path(@user) -- @no_container = true = content_for :meta_tags do = auto_discovery_link_tag(:atom, user_url(@user, format: :atom), title: "#{@user.name} activity") diff --git a/changelogs/unreleased/36765-flash-notification.yml b/changelogs/unreleased/36765-flash-notification.yml new file mode 100644 index 00000000000..3229cf6235a --- /dev/null +++ b/changelogs/unreleased/36765-flash-notification.yml @@ -0,0 +1,5 @@ +--- +title: Make flash notifications sticky +merge_request: 30141 +author: +type: changed diff --git a/changelogs/unreleased/40096-allow-ci-token-to-delete-from-registry.yml b/changelogs/unreleased/40096-allow-ci-token-to-delete-from-registry.yml new file mode 100644 index 00000000000..3e5de08f6ae --- /dev/null +++ b/changelogs/unreleased/40096-allow-ci-token-to-delete-from-registry.yml @@ -0,0 +1,5 @@ +--- +title: Allow $CI_REGISTRY_USER to delete tags +merge_request: 31796 +author: +type: added diff --git a/changelogs/unreleased/51372-remove-milestone-tabs-deprecation-message.yml b/changelogs/unreleased/51372-remove-milestone-tabs-deprecation-message.yml new file mode 100644 index 00000000000..5f05b150b93 --- /dev/null +++ b/changelogs/unreleased/51372-remove-milestone-tabs-deprecation-message.yml @@ -0,0 +1,5 @@ +--- +title: Remove deprecation message for milestone tabs +merge_request: 32252 +author: +type: other diff --git a/changelogs/unreleased/62402-milestone-release-be.yml b/changelogs/unreleased/62402-milestone-release-be.yml new file mode 100644 index 00000000000..3b1f6edfe6b --- /dev/null +++ b/changelogs/unreleased/62402-milestone-release-be.yml @@ -0,0 +1,5 @@ +--- +title: Allow milestones to be associated with a release (backend) +merge_request: 30816 +author: +type: added diff --git a/changelogs/unreleased/change-role-system-hook.yml b/changelogs/unreleased/change-role-system-hook.yml new file mode 100644 index 00000000000..adc9e43b1f2 --- /dev/null +++ b/changelogs/unreleased/change-role-system-hook.yml @@ -0,0 +1,5 @@ +--- +title: Add system hooks for project/group membership updates +merge_request: 32371 +author: Brandon Williams +type: added diff --git a/db/fixtures/development/17_cycle_analytics.rb b/db/fixtures/development/17_cycle_analytics.rb index 78ceb74da65..9d293f425e6 100644 --- a/db/fixtures/development/17_cycle_analytics.rb +++ b/db/fixtures/development/17_cycle_analytics.rb @@ -18,6 +18,7 @@ class Gitlab::Seeder::CycleAnalytics # Milestones / Labels Timecop.travel 5.days.from_now + if index.even? issue_metrics.first_associated_with_milestone_at = rand(6..12).hours.from_now else @@ -146,7 +147,7 @@ class Gitlab::Seeder::CycleAnalytics commit_sha = issue.project.repository.create_file(@user, filename, "content", message: "Commit for #{issue.to_reference}", branch_name: branch_name) issue.project.repository.commit(commit_sha) - Git::BranchPushService.new( + ::Git::BranchPushService.new( issue.project, @user, oldrev: issue.project.repository.commit("master").sha, @@ -182,7 +183,8 @@ class Gitlab::Seeder::CycleAnalytics ref: "refs/heads/#{merge_request.source_branch}") pipeline = service.execute(:push, ignore_skip_ci: true, save_on_errors: false) - pipeline.builds.map(&:run!) + pipeline.builds.each(&:enqueue) # make sure all pipelines in pending state + pipeline.builds.each(&:run!) pipeline.update_status end end diff --git a/db/migrate/20190722144316_create_milestone_releases_table.rb b/db/migrate/20190722144316_create_milestone_releases_table.rb new file mode 100644 index 00000000000..55878bcec41 --- /dev/null +++ b/db/migrate/20190722144316_create_milestone_releases_table.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +class CreateMilestoneReleasesTable < ActiveRecord::Migration[5.2] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def up + create_table :milestone_releases do |t| + t.references :milestone, foreign_key: { on_delete: :cascade }, null: false, index: false + t.references :release, foreign_key: { on_delete: :cascade }, null: false + end + + add_index :milestone_releases, [:milestone_id, :release_id], unique: true, name: 'index_miletone_releases_on_milestone_and_release' + end + + def down + drop_table :milestone_releases + end +end diff --git a/db/migrate/20190828110802_add_not_null_constraints_to_prometheus_metrics_y_label_and_unit.rb b/db/migrate/20190828110802_add_not_null_constraints_to_prometheus_metrics_y_label_and_unit.rb new file mode 100644 index 00000000000..6f3650ca966 --- /dev/null +++ b/db/migrate/20190828110802_add_not_null_constraints_to_prometheus_metrics_y_label_and_unit.rb @@ -0,0 +1,8 @@ +class AddNotNullConstraintsToPrometheusMetricsYLabelAndUnit < ActiveRecord::Migration[5.2] + DOWNTIME = false + + def change + change_column_null(:prometheus_metrics, :y_label, false) + change_column_null(:prometheus_metrics, :unit, false) + end +end diff --git a/db/schema.rb b/db/schema.rb index 5b89cdf0b98..b24558f459e 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -2158,6 +2158,13 @@ ActiveRecord::Schema.define(version: 2019_09_02_131045) do t.index ["user_id"], name: "index_merge_trains_on_user_id" end + create_table "milestone_releases", force: :cascade do |t| + t.bigint "milestone_id", null: false + t.bigint "release_id", null: false + t.index ["milestone_id", "release_id"], name: "index_miletone_releases_on_milestone_and_release", unique: true + t.index ["release_id"], name: "index_milestone_releases_on_release_id" + end + create_table "milestones", id: :serial, force: :cascade do |t| t.string "title", null: false t.integer "project_id" @@ -2869,8 +2876,8 @@ ActiveRecord::Schema.define(version: 2019_09_02_131045) do t.integer "project_id" t.string "title", null: false t.string "query", null: false - t.string "y_label" - t.string "unit" + t.string "y_label", null: false + t.string "unit", null: false t.string "legend" t.integer "group", null: false t.datetime_with_timezone "created_at", null: false @@ -3932,6 +3939,8 @@ ActiveRecord::Schema.define(version: 2019_09_02_131045) do add_foreign_key "merge_trains", "merge_requests", on_delete: :cascade add_foreign_key "merge_trains", "projects", column: "target_project_id", on_delete: :cascade add_foreign_key "merge_trains", "users", on_delete: :cascade + add_foreign_key "milestone_releases", "milestones", on_delete: :cascade + add_foreign_key "milestone_releases", "releases", on_delete: :cascade add_foreign_key "milestones", "namespaces", column: "group_id", name: "fk_95650a40d4", on_delete: :cascade add_foreign_key "milestones", "projects", name: "fk_9bd0a0c791", on_delete: :cascade add_foreign_key "namespace_aggregation_schedules", "namespaces", on_delete: :cascade diff --git a/doc/administration/gitaly/index.md b/doc/administration/gitaly/index.md index eab4b2c6eea..53b354d2f92 100644 --- a/doc/administration/gitaly/index.md +++ b/doc/administration/gitaly/index.md @@ -148,7 +148,7 @@ Check the directory layout on your Gitaly server to be sure. <!-- updates to following example must also be made at - https://gitlab.com/charts/gitlab/blob/master/doc/advanced/external-gitaly/external-omnibus-gitaly.md#configure-omnibus-gitlab + https://gitlab.com/gitlab-org/charts/gitlab/blob/master/doc/advanced/external-gitaly/external-omnibus-gitaly.md#configure-omnibus-gitlab --> ```ruby diff --git a/doc/administration/troubleshooting/kubernetes_cheat_sheet.md b/doc/administration/troubleshooting/kubernetes_cheat_sheet.md index 260af333e8e..6bcd4c48e5a 100644 --- a/doc/administration/troubleshooting/kubernetes_cheat_sheet.md +++ b/doc/administration/troubleshooting/kubernetes_cheat_sheet.md @@ -75,7 +75,7 @@ and they will assist you with any issues you are having. ## GitLab-specific kubernetes information - Minimal config that can be used to test a Kubernetes helm chart can be found - [here](https://gitlab.com/charts/gitlab/issues/620). + [here](https://gitlab.com/gitlab-org/charts/gitlab/issues/620). - Tailing logs of a separate pod. An example for a unicorn pod: @@ -176,7 +176,7 @@ and they will assist you with any issues you are having. helm upgrade <release name> <chart path> -f gitlab.yaml ``` - After <https://canary.gitlab.com/charts/gitlab/issues/780> is fixed, it should + After <https://gitlab.com/gitlab-org/charts/gitlab/issues/780> is fixed, it should be possible to use [Updating GitLab using the Helm Chart](https://docs.gitlab.com/ee/install/kubernetes/gitlab_chart.html#updating-gitlab-using-the-helm-chart) for upgrades. @@ -191,8 +191,8 @@ and they will assist you with any issues you are having. ## Installation of minimal GitLab config via Minukube on macOS -This section is based on [Developing for Kubernetes with Minikube](https://gitlab.com/charts/gitlab/blob/master/doc/minikube/index.md) -and [Helm](https://gitlab.com/charts/gitlab/blob/master/doc/helm/index.md). Refer +This section is based on [Developing for Kubernetes with Minikube](https://gitlab.com/gitlab-org/charts/gitlab/blob/master/doc/minikube/index.md) +and [Helm](https://gitlab.com/gitlab-org/charts/gitlab/blob/master/doc/helm/index.md). Refer to those documents for details. - Install Kubectl via Homebrew: @@ -223,7 +223,7 @@ to those documents for details. helm init --service-account tiller ``` -- Copy the file <https://gitlab.com/charts/gitlab/raw/master/examples/values-minikube-minimum.yaml> +- Copy the file <https://gitlab.com/gitlab-org/charts/gitlab/raw/master/examples/values-minikube-minimum.yaml> to your workstation. - Find the IP address in the output of `minikube ip` and update the yaml file with diff --git a/doc/api/releases/index.md b/doc/api/releases/index.md index 850cf57a06f..8d5b3a65789 100644 --- a/doc/api/releases/index.md +++ b/doc/api/releases/index.md @@ -57,6 +57,19 @@ Example response: "committer_email":"admin@example.com", "committed_date":"2019-01-03T01:55:38.000Z" }, + "milestone":{ + "id":51, + "iid":1, + "project_id":24, + "title":"v1.0-rc", + "description":"Voluptate fugiat possimus quis quod aliquam expedita.", + "state":"closed", + "created_at":"2019-07-12T19:45:44.256Z", + "updated_at":"2019-07-12T19:45:44.256Z", + "due_date":"2019-08-16T11:00:00.256Z", + "start_date":"2019-07-30T12:00:00.256Z", + "web_url":"http://localhost:3000/root/awesome-app/-/milestones/1" + }, "assets":{ "count":6, "sources":[ @@ -205,6 +218,19 @@ Example response: "committer_email":"admin@example.com", "committed_date":"2019-01-03T01:53:28.000Z" }, + "milestone":{ + "id":51, + "iid":1, + "project_id":24, + "title":"v1.0-rc", + "description":"Voluptate fugiat possimus quis quod aliquam expedita.", + "state":"closed", + "created_at":"2019-07-12T19:45:44.256Z", + "updated_at":"2019-07-12T19:45:44.256Z", + "due_date":"2019-08-16T11:00:00.256Z", + "start_date":"2019-07-30T12:00:00.256Z", + "web_url":"http://localhost:3000/root/awesome-app/-/milestones/1" + }, "assets":{ "count":4, "sources":[ @@ -240,23 +266,24 @@ Create a Release. You need push access to the repository to create a Release. POST /projects/:id/releases ``` -| Attribute | Type | Required | Description | -| -------------------| -------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------- | -| `id` | integer/string | yes | The ID or [URL-encoded path of the project](../README.md#namespaced-path-encoding). | -| `name` | string | yes | The release name. | -| `tag_name` | string | yes | The tag where the release will be created from. | -| `description` | string | yes | The description of the release. You can use [markdown](../../user/markdown.md). | -| `ref` | string | no | If `tag_name` doesn't exist, the release will be created from `ref`. It can be a commit SHA, another tag name, or a branch name. | -| `assets:links` | array of hash | no | An array of assets links. | -| `assets:links:name`| string | required by: `assets:links` | The name of the link. | -| `assets:links:url` | string | required by: `assets:links` | The url of the link. | -| `released_at` | datetime | no | The date when the release will be/was ready. Defaults to the current time. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`). | +| Attribute | Type | Required | Description | +| -------------------| --------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------- | +| `id` | integer/string | yes | The ID or [URL-encoded path of the project](../README.md#namespaced-path-encoding). | +| `name` | string | yes | The release name. | +| `tag_name` | string | yes | The tag where the release will be created from. | +| `description` | string | yes | The description of the release. You can use [markdown](../../user/markdown.md). | +| `ref` | string | no | If `tag_name` doesn't exist, the release will be created from `ref`. It can be a commit SHA, another tag name, or a branch name. | +| `milestone` | string | no | The title of the milestone the release is associated with. | +| `assets:links` | array of hash | no | An array of assets links. | +| `assets:links:name`| string | required by: `assets:links` | The name of the link. | +| `assets:links:url` | string | required by: `assets:links` | The url of the link. | +| `released_at` | datetime | no | The date when the release will be/was ready. Defaults to the current time. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`). | Example request: ```sh curl --header 'Content-Type: application/json' --header "PRIVATE-TOKEN: gDybLx3yrUK_HLp3qPjS" \ - --data '{ "name": "New release", "tag_name": "v0.3", "description": "Super nice release", "assets": { "links": [{ "name": "hoge", "url": "https://google.com" }] } }' \ + --data '{ "name": "New release", "tag_name": "v0.3", "description": "Super nice release", "milestone": "v1.0-rc", "assets": { "links": [{ "name": "hoge", "url": "https://google.com" }] } }' \ --request POST https://gitlab.example.com/api/v4/projects/24/releases ``` @@ -294,6 +321,19 @@ Example response: "committer_email":"admin@example.com", "committed_date":"2019-01-03T01:55:38.000Z" }, + "milestone":{ + "id":51, + "iid":1, + "project_id":24, + "title":"v1.0-rc", + "description":"Voluptate fugiat possimus quis quod aliquam expedita.", + "state":"active", + "created_at":"2019-07-12T19:45:44.256Z", + "updated_at":"2019-07-12T19:45:44.256Z", + "due_date":"2019-08-16T11:00:00.256Z", + "start_date":"2019-07-30T12:00:00.256Z", + "web_url":"http://localhost:3000/root/awesome-app/-/milestones/1" + }, "assets":{ "count":5, "sources":[ @@ -334,18 +374,19 @@ Update a Release. PUT /projects/:id/releases/:tag_name ``` -| Attribute | Type | Required | Description | -| ------------- | -------------- | -------- | -------------------------------------------------------------------------------------------------- | -| `id` | integer/string | yes | The ID or [URL-encoded path of the project](../README.md#namespaced-path-encoding). | -| `tag_name` | string | yes | The tag where the release will be created from. | -| `name` | string | no | The release name. | -| `description` | string | no | The description of the release. You can use [markdown](../../user/markdown.md). | -| `released_at` | datetime | no | The date when the release will be/was ready. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`). | +| Attribute | Type | Required | Description | +| ------------- | -------------- | -------- | --------------------------------------------------------------------------------------------------------- | +| `id` | integer/string | yes | The ID or [URL-encoded path of the project](../README.md#namespaced-path-encoding). | +| `tag_name` | string | yes | The tag where the release will be created from. | +| `name` | string | no | The release name. | +| `description` | string | no | The description of the release. You can use [markdown](../../user/markdown.md). | +| `milestone` | string | no | The title of the milestone to associate with the release (`""` to remove the milestone from the release). | +| `released_at` | datetime | no | The date when the release will be/was ready. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`). | Example request: ```sh -curl --request PUT --data name="new name" --header "PRIVATE-TOKEN: gDybLx3yrUK_HLp3qPjS" "https://gitlab.example.com/api/v4/projects/24/releases/v0.1" +curl --header 'Content-Type: application/json' --request PUT --data '{"name": "new name", "milestone": "v1.0"}' --header "PRIVATE-TOKEN: gDybLx3yrUK_HLp3qPjS" "https://gitlab.example.com/api/v4/projects/24/releases/v0.1" ``` Example response: @@ -382,6 +423,19 @@ Example response: "committer_email":"admin@example.com", "committed_date":"2019-01-03T01:53:28.000Z" }, + "milestone":{ + "id":53, + "iid":2, + "project_id":24, + "title":"v1.0", + "description":"Voluptate fugiat possimus quis quod aliquam expedita.", + "state":"active", + "created_at":"2019-09-01T13:00:00.256Z", + "updated_at":"2019-09-01T13:00:00.256Z", + "due_date":"2019-09-20T13:00:00.256Z", + "start_date":"2019-09-05T12:00:00.256Z", + "web_url":"http://localhost:3000/root/awesome-app/-/milestones/3" + }, "assets":{ "count":4, "sources":[ diff --git a/doc/ci/docker/using_docker_build.md b/doc/ci/docker/using_docker_build.md index 730e46f994e..a48da557e09 100644 --- a/doc/ci/docker/using_docker_build.md +++ b/doc/ci/docker/using_docker_build.md @@ -580,9 +580,10 @@ For private and internal projects: If you want to use your own Docker images for docker-in-docker there are a few things you need to do in addition to the steps in the [docker-in-docker](#use-docker-in-docker-workflow-with-docker-executor) section: 1. Update the `image` and `service` to point to your registry. -1. Add a service [alias](https://docs.gitlab.com/ee/ci/yaml/#servicesalias) +1. Add a service [alias](https://docs.gitlab.com/ee/ci/yaml/#servicesalias). -Below is an example of how your `.gitlab-ci.yml` should look like, assuming you have it configured with [TLS enabled](#tls-enabled): +Below is an example of what your `.gitlab-ci.yml` should look like, +assuming you have it configured with [TLS enabled](#tls-enabled): ```yaml build: @@ -603,7 +604,7 @@ Below is an example of how your `.gitlab-ci.yml` should look like, assuming you - docker run my-docker-image /script/to/run/tests ``` -If you forget to set the service alias the `docker:19.03.1` image won't find the +If you forget to set the service alias, the `docker:19.03.1` image won't find the `dind` service, and an error like the following is thrown: ```sh diff --git a/doc/development/README.md b/doc/development/README.md index 3912a828dec..bbe73570f49 100644 --- a/doc/development/README.md +++ b/doc/development/README.md @@ -22,6 +22,7 @@ description: 'Learn how to contribute to GitLab.' - [Guidelines for implementing Enterprise Edition features](ee_features.md) - [Security process for developers](https://gitlab.com/gitlab-org/release/docs/blob/master/general/security/developer.md#security-releases-critical-non-critical-as-a-developer) - [Requesting access to Chatops on GitLab.com](chatops_on_gitlabcom.md#requesting-access) (for GitLabbers) +- [Danger bot](dangerbot.md) ## UX and Frontend guides diff --git a/doc/development/architecture.md b/doc/development/architecture.md index 2adca2dae28..ee5fc553e27 100644 --- a/doc/development/architecture.md +++ b/doc/development/architecture.md @@ -651,7 +651,7 @@ We've also detailed [our architecture of GitLab.com](https://about.gitlab.com/ha [shell-charts]: https://docs.gitlab.com/charts/charts/gitlab/gitlab-shell/ [shell-source]: ../install/installation.md#install-gitlab-shell [pages-omnibus]: ../administration/pages/index.md -[pages-charts]: https://gitlab.com/charts/gitlab/issues/37 +[pages-charts]: https://gitlab.com/gitlab-org/charts/gitlab/issues/37 [pages-source]: ../install/installation.md#install-gitlab-pages [pages-gdk]: https://gitlab.com/gitlab-org/gitlab-development-kit/blob/master/doc/howto/pages.md [registry-omnibus]: ../administration/container_registry.md#container-registry-domain-configuration @@ -673,9 +673,9 @@ We've also detailed [our architecture of GitLab.com](https://about.gitlab.com/ha [grafana-omnibus]: ../administration/monitoring/performance/grafana_configuration.md [grafana-charts]: https://github.com/helm/charts/tree/master/stable/grafana [sentry-omnibus]: https://docs.gitlab.com/omnibus/settings/configuration.html#error-reporting-and-logging-with-sentry -[sentry-charts]: https://gitlab.com/charts/gitlab/issues/1319 +[sentry-charts]: https://gitlab.com/gitlab-org/charts/gitlab/issues/1319 [jaeger-omnibus]: https://gitlab.com/gitlab-org/omnibus-gitlab/issues/4104 -[jaeger-charts]: https://gitlab.com/charts/gitlab/issues/1320 +[jaeger-charts]: https://gitlab.com/gitlab-org/charts/gitlab/issues/1320 [jaeger-source]: ../development/distributed_tracing.md#enabling-distributed-tracing [jaeger-gdk]: ../development/distributed_tracing.html#using-jaeger-in-the-gitlab-development-kit [redis-exporter-omnibus]: ../administration/monitoring/prometheus/redis_exporter.md @@ -687,7 +687,7 @@ We've also detailed [our architecture of GitLab.com](https://about.gitlab.com/ha [gitlab-monitor-omnibus]: ../administration/monitoring/prometheus/gitlab_monitor_exporter.md [gitlab-monitor-charts]: https://docs.gitlab.com/charts/charts/gitlab/gitlab-monitor/index.html [node-exporter-omnibus]: ../administration/monitoring/prometheus/node_exporter.md -[node-exporter-charts]: https://gitlab.com/charts/gitlab/issues/1332 +[node-exporter-charts]: https://gitlab.com/gitlab-org/charts/gitlab/issues/1332 [mattermost-omnibus]: https://docs.gitlab.com/omnibus/gitlab-mattermost/ [mattermost-charts]: https://docs.mattermost.com/install/install-mmte-helm-gitlab-helm.html [minio-omnibus]: https://min.io/download @@ -705,7 +705,7 @@ We've also detailed [our architecture of GitLab.com](https://about.gitlab.com/ha [certificate-management-source]: ../install/installation.md#using-https [certificate-management-gdk]: https://gitlab.com/gitlab-org/gitlab-development-kit/blob/master/doc/howto/https.md [geo-omnibus]: ../administration/geo/replication/index.md#setup-instructions -[geo-charts]: https://gitlab.com/charts/gitlab/issues/8 +[geo-charts]: https://gitlab.com/gitlab-org/charts/gitlab/issues/8 [geo-gdk]: https://gitlab.com/gitlab-org/gitlab-development-kit/blob/master/doc/howto/geo.md [ldap-omnibus]: ../administration/auth/ldap.md [ldap-charts]: https://docs.gitlab.com/charts/charts/globals.html#ldap diff --git a/doc/development/code_review.md b/doc/development/code_review.md index b7d74b17eb3..80890d96159 100644 --- a/doc/development/code_review.md +++ b/doc/development/code_review.md @@ -34,7 +34,7 @@ more than one approval, the last maintainer to review and approve it will also m ### Reviewer roulette -The `danger-review` CI job will randomly pick a reviewer and a maintainer for +The [Danger bot](dangerbot.md) randomly picks a reviewer and a maintainer for each area of the codebase that your merge request seems to touch. It only makes recommendations - feel free to override it if you think someone else is a better fit! diff --git a/doc/development/dangerbot.md b/doc/development/dangerbot.md new file mode 100644 index 00000000000..5fc5886e3a2 --- /dev/null +++ b/doc/development/dangerbot.md @@ -0,0 +1,116 @@ +# Danger bot + +The GitLab CI pipeline includes a `danger-review` job that uses [Danger](https://github.com/danger/danger) +to perform a variety of automated checks on the code under test. + +Danger is a gem that runs in the CI environment, like any other analysis tool. +What sets it apart from, e.g., Rubocop, is that it's designed to allow you to +easily write arbitrary code to test properties of your code or changes. To this +end, it provides a set of common helpers and access to information about what +has actually changed in your environment, then simply runs your code! + +If Danger is asking you to change something about your merge request, it's best +just to make the change. If you want to learn how Danger works, or make changes +to the existing rules, then this is the document for you. + +## Operation + +On startup, Danger reads a [`Dangerfile`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/Dangerfile) +from the project root. GitLab's Danger code is decomposed into a set of helpers +and plugins, all within the [`danger/`](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/danger/) +subdirectory, so ours just tells Danger to load it all. Danger will then run +each plugin against the merge request, collecting the output from each. A plugin +may output notifications, warnings, or errors, all of which are copied to the +CI job's log. If an error happens, the CI job (and so the entire pipeline) will +be failed. + +On merge requests, Danger will also copy the output to a comment on the MR +itself, increasing visibility. + +## Development guidelines + +Danger code is Ruby code, so all our [usual backend guidelines](README.md#backend-guides) +continue to apply. However, there are a few things that deserve special emphasis. + +### When to use Danger + +Danger is a powerful tool and flexible tool, but not always the most appropriate +way to solve a given problem or workflow. + +First, be aware of GitLab's [commitment to dogfooding](https://about.gitlab.com/handbook/engineering/#dogfooding). +The code we write for Danger is GitLab-specific, and it **may not** be most +appropriate place to implement functionality that addresses a need we encounter. +Our users, customers, and even our own satellite projects, such as [Gitaly](https://gitlab.com/gitlab-org/gitaly), +often face similar challenges, after all. Think about how you could fulfil the +same need while ensuring everyone can benefit from the work, and do that instead +if you can. + +If a standard tool (e.g. `rubocop`) exists for a task, it is better to use it +directly, rather than calling it via Danger. Running and debugging the results +of those tools locally is easier if Danger isn't involved, and unless you're +using some Danger-specific functionality, there's no benefit to including it in +the Danger run. + +Danger is well-suited to prototyping and rapidly iterating on solutions, so if +what we want to build is unclear, a solution in Danger can be thought of as a +trial run to gather information about a product area. If you're doing this, make +sure the problem you're trying to solve, and the outcomes of that prototyping, +are captured in an issue or epic as you go along. This will help us to address +the need as part of the product in a future version of GitLab! + +### Implementation details + +Implement each task as an isolated piece of functionality and place it in its +own directory under `danger` as `danger/<task-name>/Dangerfile`. + +Add a line to the top-level `Dangerfile` to ensure it is loaded like: + +```ruby +danger.import_dangerfile('danger/<task-name>') +``` + +Each task should be isolated from the others, and able to function in isolation. +If there is code that should be shared between multiple tasks, add a plugin to +`danger/plugins/...` and require it in each task that needs it. You can also +create plugins that are specific to a single task, which is a natural place for +complex logic related to that task. + +Danger code is just Ruby code. It should adhere to our coding standards, and +needs tests, like any other piece of Ruby in our codebase. However, we aren't +able to test a `Dangerfile` directly! So, to maximise test coverage, try to +minimize the number of lines of code in `danger/`. A non-trivial `Dangerfile` +should mostly call plugin code with arguments derived from the methods provided +by Danger. The plugin code itself should have unit tests. + +At present, we do this by putting the code in a module in `lib/gitlab/danger/...`, +and including it in the matching `danger/plugins/...` file. Specs can then be +added in `spec/lib/gitlab/danger/...`. + +You'll only know if your `Dangerfile` works by pushing the branch that contains +it to GitLab. This can be quite frustrating, as it significantly increases the +cycle time when developing a new task, or trying to debug something in an +existing one. If you've followed the guidelines above, most of your code can +be exercised locally in RSpec, minimizing the number of cycles you need to go +through in CI. However, you can speed these cycles up somewhat by emptying the +`.gitlab/ci/rails.gitlab-ci.yml` file in your merge request. Just don't forget +to revert the change before merging! + +You should add the `~Danger bot` label to the merge request before sending it +for review. + +## Current uses + +Here is a (non-exhaustive) list of the kinds of things Danger has been used for +at GitLab so far: + +- Coding style +- Database review workflow +- Documentation review workflow +- Merge request metrics +- Reviewer roulette workflow +- Single codebase effort + +## Limitations + +- [`danger local` does not work on GitLab](https://github.com/danger/danger/issues/458) +- Danger output is not added to a merge request comment if working on a fork. diff --git a/doc/development/testing_guide/end_to_end/index.md b/doc/development/testing_guide/end_to_end/index.md index 3ae3ce183d9..f6a2f642274 100644 --- a/doc/development/testing_guide/end_to_end/index.md +++ b/doc/development/testing_guide/end_to_end/index.md @@ -118,7 +118,7 @@ Helm chart][helm-chart], itself deployed with custom See [Review Apps][review-apps] for more details about Review Apps. -[helm-chart]: https://gitlab.com/charts/gitlab/ +[helm-chart]: https://gitlab.com/gitlab-org/charts/gitlab/ [cng]: https://gitlab.com/gitlab-org/build/CNG ## How do I run the tests? diff --git a/doc/development/testing_guide/review_apps.md b/doc/development/testing_guide/review_apps.md index 28a60660995..13772cbe015 100644 --- a/doc/development/testing_guide/review_apps.md +++ b/doc/development/testing_guide/review_apps.md @@ -267,7 +267,7 @@ find a way to limit it to only us.** - [Review Apps integration for CE/EE (presentation)](https://docs.google.com/presentation/d/1QPLr6FO4LduROU8pQIPkX1yfGvD13GEJIBOenqoKxR8/edit?usp=sharing) -[charts-1068]: https://gitlab.com/charts/gitlab/issues/1068 +[charts-1068]: https://gitlab.com/gitlab-org/charts/gitlab/issues/1068 [gitlab-pipeline]: https://gitlab.com/gitlab-org/gitlab-ce/pipelines/44362587 [gitlab:assets:compile]: https://gitlab.com/gitlab-org/gitlab-ce/-/jobs/149511610 [review-build-cng]: https://gitlab.com/gitlab-org/gitlab-ce/-/jobs/149511623 @@ -276,7 +276,7 @@ find a way to limit it to only us.** [cng]: https://gitlab.com/gitlab-org/build/CNG [cng-mirror-pipeline]: https://gitlab.com/gitlab-org/build/CNG-mirror/pipelines/44364657 [cng-mirror-registry]: https://gitlab.com/gitlab-org/build/CNG-mirror/container_registry -[helm-chart]: https://gitlab.com/charts/gitlab/ +[helm-chart]: https://gitlab.com/gitlab-org/charts/gitlab/ [review-apps-ce]: https://console.cloud.google.com/kubernetes/clusters/details/us-central1-a/review-apps-ce?project=gitlab-review-apps [review-apps-ee]: https://console.cloud.google.com/kubernetes/clusters/details/us-central1-b/review-apps-ee?project=gitlab-review-apps [review-apps.sh]: https://gitlab.com/gitlab-org/gitlab-ee/blob/master/scripts/review_apps/review-apps.sh diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md index 336000f6cb9..59b775d13bd 100644 --- a/doc/raketasks/backup_restore.md +++ b/doc/raketasks/backup_restore.md @@ -92,9 +92,9 @@ If you are running GitLab within a Docker container, you can run the backup from docker exec -t <container name> gitlab-backup create ``` -If you are using the [GitLab helm chart](https://gitlab.com/charts/gitlab) on a +If you are using the [GitLab helm chart](https://gitlab.com/gitlab-org/charts/gitlab) on a Kubernetes cluster, you can run the backup task using `backup-utility` script on -the GitLab task runner pod via `kubectl`. Refer to [backing up a GitLab installation](https://gitlab.com/charts/gitlab/blob/master/doc/backup-restore/backup.md#backing-up-a-gitlab-installation) for more details: +the GitLab task runner pod via `kubectl`. Refer to [backing up a GitLab installation](https://gitlab.com/gitlab-org/charts/gitlab/blob/master/doc/backup-restore/backup.md#backing-up-a-gitlab-installation) for more details: ```sh kubectl exec -it <gitlab task-runner pod> backup-utility @@ -764,7 +764,7 @@ docker exec -it <name of container> gitlab-backup restore ``` The GitLab helm chart uses a different process, documented in -[restoring a GitLab helm chart installation](https://gitlab.com/charts/gitlab/blob/master/doc/backup-restore/restore.md). +[restoring a GitLab helm chart installation](https://gitlab.com/gitlab-org/charts/gitlab/blob/master/doc/backup-restore/restore.md). ## Alternative backup strategies diff --git a/doc/system_hooks/system_hooks.md b/doc/system_hooks/system_hooks.md index 1e9eb15533a..24a334e7067 100644 --- a/doc/system_hooks/system_hooks.md +++ b/doc/system_hooks/system_hooks.md @@ -13,6 +13,7 @@ Your GitLab instance can perform HTTP POST requests on the following events: - `project_update` - `user_add_to_team` - `user_remove_from_team` +- `user_update_for_team` - `user_create` - `user_destroy` - `user_failed_login` @@ -24,6 +25,7 @@ Your GitLab instance can perform HTTP POST requests on the following events: - `group_rename` - `user_add_to_group` - `user_remove_from_group` +- `user_update_for_group` The triggers for most of these are self-explanatory, but `project_update` and `project_rename` deserve some clarification: `project_update` is fired any time an attribute of a project is changed (name, description, tags, etc.) *unless* the `path` attribute is also changed. In that case, a `project_rename` is triggered instead (so that, for instance, if all you care about is the repo URL, you can just listen for `project_rename`). @@ -173,6 +175,26 @@ Please refer to `group_rename` and `user_rename` for that case. } ``` +**Team Member Updated:** + +```json +{ + "created_at": "2012-07-21T07:30:56Z", + "updated_at": "2012-07-21T07:38:22Z", + "event_name": "user_update_for_team", + "access_level": "Maintainer", + "project_id": 74, + "project_name": "StoreCloud", + "project_path": "storecloud", + "project_path_with_namespace": "jsmith/storecloud", + "user_email": "johnsmith@gmail.com", + "user_name": "John Smith", + "user_username": "johnsmith", + "user_id": 41, + "project_visibility": "visibilitylevel|private" +} +``` + **User created:** ```json @@ -349,6 +371,24 @@ If the user is blocked via LDAP, `state` will be `ldap_blocked`. } ``` +**Group Member Updated:** + +```json +{ + "created_at": "2012-07-21T07:30:56Z", + "updated_at": "2012-07-21T07:38:22Z", + "event_name": "user_update_for_group", + "group_access": "Maintainer", + "group_id": 78, + "group_name": "StoreCloud", + "group_path": "storecloud", + "user_email": "johnsmith@gmail.com", + "user_name": "John Smith", + "user_username": "johnsmith", + "user_id": 41 +} +``` + ## Push events Triggered when you push to the repository, except when pushing tags. diff --git a/doc/user/project/quick_actions.md b/doc/user/project/quick_actions.md index e373d605098..1f7f85e9750 100644 --- a/doc/user/project/quick_actions.md +++ b/doc/user/project/quick_actions.md @@ -14,8 +14,11 @@ on a separate line in order to be properly detected and executed. Once executed, ## Quick Actions for issues, merge requests and epics -The following quick actions are applicable to descriptions, discussions and threads -in issues and merge requests, as well as epics.**(ULTIMATE)** +The following quick actions are applicable to descriptions, discussions and threads in: + +- Issues +- Merge requests +- Epics **(ULTIMATE)** | Command | Issue | Merge request | Epic | Action | |:--------------------------------------|:------|:--------------|:-----|:------ | diff --git a/lib/api/entities.rb b/lib/api/entities.rb index cfcf6228225..ba58e125568 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -1229,6 +1229,7 @@ module API expose :author, using: Entities::UserBasic, if: -> (release, _) { release.author.present? } expose :commit, using: Entities::Commit, if: lambda { |_, _| can_download_code? } expose :upcoming_release?, as: :upcoming_release + expose :milestone, using: Entities::Milestone, if: -> (release, _) { release.milestone.present? } expose :assets do expose :assets_count, as: :count do |release, _| diff --git a/lib/api/releases.rb b/lib/api/releases.rb index 7a3d804c30c..11a9a085068 100644 --- a/lib/api/releases.rb +++ b/lib/api/releases.rb @@ -54,6 +54,7 @@ module API requires :url, type: String end end + optional :milestone, type: String, desc: 'The title of the related milestone' optional :released_at, type: DateTime, desc: 'The date when the release will be/was ready. Defaults to the current time.' end post ':id/releases' do @@ -79,6 +80,7 @@ module API optional :name, type: String, desc: 'The name of the release' optional :description, type: String, desc: 'Release notes with markdown support' optional :released_at, type: DateTime, desc: 'The date when the release will be/was ready.' + optional :milestone, type: String, desc: 'The title of the related milestone' end put ':id/releases/:tag_name', requirements: RELEASE_ENDPOINT_REQUIREMETS do authorize_update_release! diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb index 6769bd95c2b..bdc46abeb9f 100644 --- a/lib/gitlab/auth.rb +++ b/lib/gitlab/auth.rb @@ -265,7 +265,8 @@ module Gitlab :read_project, :build_download_code, :build_read_container_image, - :build_create_container_image + :build_create_container_image, + :build_destroy_container_image ] end diff --git a/lib/gitlab/correlation_id.rb b/lib/gitlab/correlation_id.rb deleted file mode 100644 index 0f9bde4390e..00000000000 --- a/lib/gitlab/correlation_id.rb +++ /dev/null @@ -1,40 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module CorrelationId - LOG_KEY = 'correlation_id'.freeze - - class << self - def use_id(correlation_id, &blk) - # always generate a id if null is passed - correlation_id ||= new_id - - ids.push(correlation_id || new_id) - - begin - yield(current_id) - ensure - ids.pop - end - end - - def current_id - ids.last - end - - def current_or_new_id - current_id || new_id - end - - private - - def ids - Thread.current[:correlation_id] ||= [] - end - - def new_id - SecureRandom.uuid - end - end - end -end diff --git a/lib/gitlab/optimistic_locking.rb b/lib/gitlab/optimistic_locking.rb index 0c0f46d3b77..d51d718c826 100644 --- a/lib/gitlab/optimistic_locking.rb +++ b/lib/gitlab/optimistic_locking.rb @@ -4,7 +4,8 @@ module Gitlab module OptimisticLocking module_function - def retry_lock(subject, retries = 100, &block) + def retry_lock(subject, retries = nil, &block) + retries ||= 100 # TODO(Observability): We should be recording details of the number of retries and the duration of the total execution here ActiveRecord::Base.transaction do yield(subject) diff --git a/lib/gitlab/tracing.rb b/lib/gitlab/tracing.rb index 29517591c51..7732d7c9d9c 100644 --- a/lib/gitlab/tracing.rb +++ b/lib/gitlab/tracing.rb @@ -30,7 +30,7 @@ module Gitlab # Avoid using `format` since it can throw TypeErrors # which we want to avoid on unsanitised env var input tracing_url_template.to_s - .gsub(/\{\{\s*correlation_id\s*\}\}/, Gitlab::CorrelationId.current_id.to_s) + .gsub(/\{\{\s*correlation_id\s*\}\}/, Labkit::Correlation::CorrelationId.current_id.to_s) .gsub(/\{\{\s*service\s*\}\}/, Gitlab.process_name) end end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index a1836646b1a..a5619079988 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -5334,9 +5334,6 @@ msgstr "" msgid "GitLab.com import" msgstr "" -msgid "GitLab’s issue tracker" -msgstr "" - msgid "Gitaly" msgstr "" @@ -6586,9 +6583,6 @@ msgstr "" msgid "Learn more" msgstr "" -msgid "Learn more about %{issue_boards_url}, to keep track of issues in multiple lists, using labels, assignees, and milestones. If you’re missing something from issue boards, please create an issue on %{gitlab_issues_url}." -msgstr "" - msgid "Learn more about Auto DevOps" msgstr "" @@ -8188,6 +8182,9 @@ msgstr "" msgid "Pipeline|Coverage" msgstr "" +msgid "Pipeline|Detached merge request pipeline" +msgstr "" + msgid "Pipeline|Duration" msgstr "" @@ -8197,6 +8194,12 @@ msgstr "" msgid "Pipeline|Key" msgstr "" +msgid "Pipeline|Merge train pipeline" +msgstr "" + +msgid "Pipeline|Merged result pipeline" +msgstr "" + msgid "Pipeline|Pipeline" msgstr "" @@ -11553,9 +11556,6 @@ msgstr "" msgid "The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time." msgstr "" -msgid "The tabs below will be removed in a future version" -msgstr "" - msgid "The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running." msgstr "" @@ -13814,9 +13814,6 @@ msgstr "" msgid "issue" msgstr "" -msgid "issue boards" -msgstr "" - msgid "it is stored externally" msgstr "" diff --git a/scripts/review_apps/review-apps.sh b/scripts/review_apps/review-apps.sh index 4935c1342a3..fc5b57451de 100755 --- a/scripts/review_apps/review-apps.sh +++ b/scripts/review_apps/review-apps.sh @@ -164,7 +164,7 @@ function create_application_secret() { function download_chart() { echoinfo "Downloading the GitLab chart..." true - curl -o gitlab.tar.bz2 "https://gitlab.com/charts/gitlab/-/archive/${GITLAB_HELM_CHART_REF}/gitlab-${GITLAB_HELM_CHART_REF}.tar.bz2" + curl -o gitlab.tar.bz2 "https://gitlab.com/gitlab-org/charts/gitlab/-/archive/${GITLAB_HELM_CHART_REF}/gitlab-${GITLAB_HELM_CHART_REF}.tar.bz2" tar -xjf gitlab.tar.bz2 cd "gitlab-${GITLAB_HELM_CHART_REF}" diff --git a/spec/factories/milestone_releases.rb b/spec/factories/milestone_releases.rb new file mode 100644 index 00000000000..08e109480ab --- /dev/null +++ b/spec/factories/milestone_releases.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :milestone_release do + milestone + release + + before(:create, :build) do |mr| + project = create(:project) + mr.milestone.project = project + mr.release.project = project + end + end +end diff --git a/spec/fixtures/api/schemas/pipeline.json b/spec/fixtures/api/schemas/pipeline.json index b6e30c40f13..d9ffad8fbab 100644 --- a/spec/fixtures/api/schemas/pipeline.json +++ b/spec/fixtures/api/schemas/pipeline.json @@ -97,6 +97,10 @@ "id": "/properties/details/properties/finished_at", "type": "string" }, + "name": { + "id": "/properties/details/properties/name", + "type": "string" + }, "manual_actions": { "id": "/properties/details/properties/manual_actions", "items": {}, @@ -323,6 +327,10 @@ "id": "/properties/web_url", "type": "string" }, + "merge_request_event_type": { + "id": "/properties/merge_request_event_type", + "type": "string" + }, "user": { "id": "/properties/user", "properties": { diff --git a/spec/fixtures/api/schemas/public_api/v4/release.json b/spec/fixtures/api/schemas/public_api/v4/release.json index ec3fa59cdb1..078b1c0b982 100644 --- a/spec/fixtures/api/schemas/public_api/v4/release.json +++ b/spec/fixtures/api/schemas/public_api/v4/release.json @@ -15,6 +15,7 @@ "author": { "oneOf": [{ "type": "null" }, { "$ref": "user/basic.json" }] }, + "milestone": { "type": "string" }, "assets": { "required": ["count", "links", "sources"], "properties": { diff --git a/spec/javascripts/flash_spec.js b/spec/javascripts/flash_spec.js index aecab331ead..bd8608b6bac 100644 --- a/spec/javascripts/flash_spec.js +++ b/spec/javascripts/flash_spec.js @@ -25,14 +25,6 @@ describe('Flash', () => { '<script>alert("a");</script>', ); }); - - it('adds container classes when inside content wrapper', () => { - el.innerHTML = createFlashEl('testing', 'alert', true); - - expect(el.querySelector('.flash-text').classList.contains('container-fluid')).toBeTruthy(); - - expect(el.querySelector('.flash-text').classList.contains('container-limited')).toBeTruthy(); - }); }); describe('hideFlash', () => { @@ -171,9 +163,7 @@ describe('Flash', () => { it('adds container classes when inside content-wrapper', () => { flash('test'); - expect(document.querySelector('.flash-text').className).toBe( - 'flash-text container-fluid container-limited limit-container-width', - ); + expect(document.querySelector('.flash-text').className).toBe('flash-text'); }); it('does not add container when outside of content-wrapper', () => { diff --git a/spec/javascripts/monitoring/charts/area_spec.js b/spec/javascripts/monitoring/charts/area_spec.js deleted file mode 100644 index 1e49a955815..00000000000 --- a/spec/javascripts/monitoring/charts/area_spec.js +++ /dev/null @@ -1,265 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import { createStore } from '~/monitoring/stores'; -import { GlLink } from '@gitlab/ui'; -import { GlAreaChart, GlChartSeriesLabel } from '@gitlab/ui/dist/charts'; -import { shallowWrapperContainsSlotText } from 'spec/helpers/vue_test_utils_helper'; -import Area from '~/monitoring/components/charts/area.vue'; -import * as types from '~/monitoring/stores/mutation_types'; -import { TEST_HOST } from 'spec/test_constants'; -import MonitoringMock, { deploymentData } from '../mock_data'; - -describe('Area component', () => { - const mockSha = 'mockSha'; - const mockWidgets = 'mockWidgets'; - const mockSvgPathContent = 'mockSvgPathContent'; - const projectPath = `${TEST_HOST}/path/to/project`; - const commitUrl = `${projectPath}/commit/${mockSha}`; - let mockGraphData; - let areaChart; - let spriteSpy; - let store; - - beforeEach(() => { - store = createStore(); - store.commit(`monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`, MonitoringMock.data); - store.commit(`monitoringDashboard/${types.RECEIVE_DEPLOYMENTS_DATA_SUCCESS}`, deploymentData); - - [mockGraphData] = store.state.monitoringDashboard.groups[0].metrics; - - areaChart = shallowMount(Area, { - propsData: { - graphData: mockGraphData, - containerWidth: 0, - deploymentData: store.state.monitoringDashboard.deploymentData, - projectPath, - }, - slots: { - default: mockWidgets, - }, - store, - }); - - spriteSpy = spyOnDependency(Area, 'getSvgIconPathContent').and.callFake( - () => new Promise(resolve => resolve(mockSvgPathContent)), - ); - }); - - afterEach(() => { - areaChart.destroy(); - }); - - it('renders chart title', () => { - expect(areaChart.find({ ref: 'graphTitle' }).text()).toBe(mockGraphData.title); - }); - - it('contains graph widgets from slot', () => { - expect(areaChart.find({ ref: 'graphWidgets' }).text()).toBe(mockWidgets); - }); - - describe('wrapped components', () => { - describe('GitLab UI area chart', () => { - let glAreaChart; - - beforeEach(() => { - glAreaChart = areaChart.find(GlAreaChart); - }); - - it('is a Vue instance', () => { - expect(glAreaChart.isVueInstance()).toBe(true); - }); - - it('receives data properties needed for proper chart render', () => { - const props = glAreaChart.props(); - - expect(props.data).toBe(areaChart.vm.chartData); - expect(props.option).toBe(areaChart.vm.chartOptions); - expect(props.formatTooltipText).toBe(areaChart.vm.formatTooltipText); - expect(props.thresholds).toBe(areaChart.vm.thresholds); - }); - - it('recieves a tooltip title', () => { - const mockTitle = 'mockTitle'; - areaChart.vm.tooltip.title = mockTitle; - - expect(shallowWrapperContainsSlotText(glAreaChart, 'tooltipTitle', mockTitle)).toBe(true); - }); - - describe('when tooltip is showing deployment data', () => { - beforeEach(() => { - areaChart.vm.tooltip.isDeployment = true; - }); - - it('uses deployment title', () => { - expect(shallowWrapperContainsSlotText(glAreaChart, 'tooltipTitle', 'Deployed')).toBe( - true, - ); - }); - - it('renders clickable commit sha in tooltip content', () => { - areaChart.vm.tooltip.sha = mockSha; - areaChart.vm.tooltip.commitUrl = commitUrl; - - const commitLink = areaChart.find(GlLink); - - expect(shallowWrapperContainsSlotText(commitLink, 'default', mockSha)).toBe(true); - expect(commitLink.attributes('href')).toEqual(commitUrl); - }); - }); - }); - }); - - describe('methods', () => { - describe('formatTooltipText', () => { - const mockDate = deploymentData[0].created_at; - const generateSeriesData = type => ({ - seriesData: [ - { - seriesName: areaChart.vm.chartData[0].name, - componentSubType: type, - value: [mockDate, 5.55555], - seriesIndex: 0, - }, - ], - value: mockDate, - }); - - describe('when series is of line type', () => { - beforeEach(() => { - areaChart.vm.formatTooltipText(generateSeriesData('line')); - }); - - it('formats tooltip title', () => { - expect(areaChart.vm.tooltip.title).toBe('31 May 2017, 9:23PM'); - }); - - it('formats tooltip content', () => { - const name = 'Core Usage'; - const value = '5.556'; - const seriesLabel = areaChart.find(GlChartSeriesLabel); - - expect(seriesLabel.vm.color).toBe(''); - expect(shallowWrapperContainsSlotText(seriesLabel, 'default', name)).toBe(true); - expect(areaChart.vm.tooltip.content).toEqual([{ name, value, color: undefined }]); - expect( - shallowWrapperContainsSlotText(areaChart.find(GlAreaChart), 'tooltipContent', value), - ).toBe(true); - }); - }); - - describe('when series is of scatter type', () => { - beforeEach(() => { - areaChart.vm.formatTooltipText(generateSeriesData('scatter')); - }); - - it('formats tooltip title', () => { - expect(areaChart.vm.tooltip.title).toBe('31 May 2017, 9:23PM'); - }); - - it('formats tooltip sha', () => { - expect(areaChart.vm.tooltip.sha).toBe('f5bcd1d9'); - }); - }); - }); - - describe('setSvg', () => { - const mockSvgName = 'mockSvgName'; - - beforeEach(() => { - areaChart.vm.setSvg(mockSvgName); - }); - - it('gets svg path content', () => { - expect(spriteSpy).toHaveBeenCalledWith(mockSvgName); - }); - - it('sets svg path content', done => { - areaChart.vm.$nextTick(() => { - expect(areaChart.vm.svgs[mockSvgName]).toBe(`path://${mockSvgPathContent}`); - done(); - }); - }); - }); - - describe('onResize', () => { - const mockWidth = 233; - - beforeEach(() => { - spyOn(Element.prototype, 'getBoundingClientRect').and.callFake(() => ({ - width: mockWidth, - })); - areaChart.vm.onResize(); - }); - - it('sets area chart width', () => { - expect(areaChart.vm.width).toBe(mockWidth); - }); - }); - }); - - describe('computed', () => { - describe('chartData', () => { - let chartData; - const seriesData = () => chartData[0]; - - beforeEach(() => { - ({ chartData } = areaChart.vm); - }); - - it('utilizes all data points', () => { - expect(chartData.length).toBe(1); - expect(seriesData().data.length).toBe(297); - }); - - it('creates valid data', () => { - const { data } = seriesData(); - - expect( - data.filter(([time, value]) => new Date(time).getTime() > 0 && typeof value === 'number') - .length, - ).toBe(data.length); - }); - - it('formats line width correctly', () => { - expect(chartData[0].lineStyle.width).toBe(2); - }); - }); - - describe('chartOptions', () => { - describe('dataZoom', () => { - it('contains an svg object within an array to properly render icon', () => { - const dataZoomObject = [{}]; - - expect(areaChart.vm.chartOptions.dataZoom).toEqual(dataZoomObject); - }); - }); - - describe('yAxis formatter', () => { - let format; - - beforeEach(() => { - format = areaChart.vm.chartOptions.yAxis.axisLabel.formatter; - }); - - it('rounds to 3 decimal places', () => { - expect(format(0.88888)).toBe('0.889'); - }); - }); - }); - - describe('scatterSeries', () => { - it('utilizes deployment data', () => { - expect(areaChart.vm.scatterSeries.data).toEqual([ - ['2017-05-31T21:23:37.881Z', 0], - ['2017-05-30T20:08:04.629Z', 0], - ['2017-05-30T17:42:38.409Z', 0], - ]); - }); - }); - - describe('yAxisLabel', () => { - it('constructs a label for the chart y-axis', () => { - expect(areaChart.vm.yAxisLabel).toBe('CPU'); - }); - }); - }); -}); diff --git a/spec/javascripts/monitoring/components/dashboard_spec.js b/spec/javascripts/monitoring/components/dashboard_spec.js index f3ec7520c6f..15e41e2fe93 100644 --- a/spec/javascripts/monitoring/components/dashboard_spec.js +++ b/spec/javascripts/monitoring/components/dashboard_spec.js @@ -378,7 +378,9 @@ describe('Dashboard', () => { }); }); - describe('link to chart', () => { + // https://gitlab.com/gitlab-org/gitlab-ce/issues/66922 + // eslint-disable-next-line jasmine/no-disabled-tests + xdescribe('link to chart', () => { let wrapper; const currentDashboard = 'TEST_DASHBOARD'; localVue.use(GlToast); diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb index 098c33f9cb1..0365d63ea9c 100644 --- a/spec/lib/gitlab/auth_spec.rb +++ b/spec/lib/gitlab/auth_spec.rb @@ -587,7 +587,8 @@ describe Gitlab::Auth do :read_project, :build_download_code, :build_read_container_image, - :build_create_container_image + :build_create_container_image, + :build_destroy_container_image ] end diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index ec4a6ef05b9..c6aa4a2482c 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -62,6 +62,8 @@ milestone: - participants - events - boards +- milestone_release +- release snippets: - author - project @@ -72,6 +74,8 @@ releases: - author - project - links +- milestone_release +- milestone links: - release project_members: @@ -484,3 +488,6 @@ lists: - board - label - list_user_preferences +milestone_releases: +- milestone +- release diff --git a/spec/lib/gitlab/tracing_spec.rb b/spec/lib/gitlab/tracing_spec.rb index db75ce2a998..e913bb600ec 100644 --- a/spec/lib/gitlab/tracing_spec.rb +++ b/spec/lib/gitlab/tracing_spec.rb @@ -59,7 +59,7 @@ describe Gitlab::Tracing do it 'returns the correct state for .tracing_url' do expect(described_class).to receive(:tracing_url_enabled?).and_return(tracing_url_enabled?) allow(described_class).to receive(:tracing_url_template).and_return(tracing_url_template) - allow(Gitlab::CorrelationId).to receive(:current_id).and_return(correlation_id) + allow(Labkit::Correlation::CorrelationId).to receive(:current_id).and_return(correlation_id) allow(Gitlab).to receive(:process_name).and_return(process_name) expect(described_class.tracing_url).to eq(tracing_url) diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 7d84d094bdf..63ca383ac4b 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -323,6 +323,25 @@ describe Ci::Pipeline, :mailer do end end + describe '#merge_train_pipeline?' do + subject { pipeline.merge_train_pipeline? } + + let!(:pipeline) do + create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request, ref: ref, target_sha: 'xxx') + end + + let(:merge_request) { create(:merge_request) } + let(:ref) { 'refs/merge-requests/1/train' } + + it { is_expected.to be_truthy } + + context 'when ref is merge ref' do + let(:ref) { 'refs/merge-requests/1/merge' } + + it { is_expected.to be_falsy } + end + end + describe '#merge_request_ref?' do subject { pipeline.merge_request_ref? } @@ -333,6 +352,48 @@ describe Ci::Pipeline, :mailer do end end + describe '#merge_train_ref?' do + subject { pipeline.merge_train_ref? } + + it 'calls Mergetrain#merge_train_ref?' do + expect(MergeRequest).to receive(:merge_train_ref?).with(pipeline.ref) + + subject + end + end + + describe '#merge_request_event_type' do + subject { pipeline.merge_request_event_type } + + before do + allow(pipeline).to receive(:merge_request_event?) { true } + end + + context 'when pipeline is merge train pipeline' do + before do + allow(pipeline).to receive(:merge_train_pipeline?) { true } + end + + it { is_expected.to eq(:merge_train) } + end + + context 'when pipeline is merge request pipeline' do + before do + allow(pipeline).to receive(:merge_request_pipeline?) { true } + end + + it { is_expected.to eq(:merged_result) } + end + + context 'when pipeline is detached merge request pipeline' do + before do + allow(pipeline).to receive(:detached_merge_request_pipeline?) { true } + end + + it { is_expected.to eq(:detached) } + end + end + describe '#legacy_detached_merge_request_pipeline?' do subject { pipeline.legacy_detached_merge_request_pipeline? } @@ -782,7 +843,8 @@ describe Ci::Pipeline, :mailer do 'CI_MERGE_REQUEST_TITLE' => merge_request.title, 'CI_MERGE_REQUEST_ASSIGNEES' => merge_request.assignee_username_list, 'CI_MERGE_REQUEST_MILESTONE' => milestone.title, - 'CI_MERGE_REQUEST_LABELS' => labels.map(&:title).join(',')) + 'CI_MERGE_REQUEST_LABELS' => labels.map(&:title).join(','), + 'CI_MERGE_REQUEST_EVENT_TYPE' => pipeline.merge_request_event_type.to_s) end context 'when source project does not exist' do diff --git a/spec/models/hooks/system_hook_spec.rb b/spec/models/hooks/system_hook_spec.rb index e0d4d2e4858..a4d202dc4f8 100644 --- a/spec/models/hooks/system_hook_spec.rb +++ b/spec/models/hooks/system_hook_spec.rb @@ -64,7 +64,7 @@ describe SystemHook do ).once end - it "project_create hook" do + it "project member create hook" do project.add_maintainer(user) expect(WebMock).to have_requested(:post, system_hook.url).with( @@ -73,7 +73,7 @@ describe SystemHook do ).once end - it "project_destroy hook" do + it "project member destroy hook" do project.add_maintainer(user) project.project_members.destroy_all # rubocop: disable DestroyAll @@ -83,6 +83,15 @@ describe SystemHook do ).once end + it "project member update hook" do + project.add_guest(user) + + expect(WebMock).to have_requested(:post, system_hook.url).with( + body: /user_update_for_team/, + headers: { 'Content-Type' => 'application/json', 'X-Gitlab-Event' => 'System Hook' } + ).once + end + it 'group create hook' do create(:group) @@ -119,6 +128,16 @@ describe SystemHook do headers: { 'Content-Type' => 'application/json', 'X-Gitlab-Event' => 'System Hook' } ).once end + + it 'group member update hook' do + group.add_guest(user) + group.add_maintainer(user) + + expect(WebMock).to have_requested(:post, system_hook.url).with( + body: /user_update_for_group/, + headers: { 'Content-Type' => 'application/json', 'X-Gitlab-Event' => 'System Hook' } + ).once + end end describe '.repository_update_hooks' do diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index d344a6d0f0d..11234982dd4 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -3195,6 +3195,40 @@ describe MergeRequest do end end + describe '.merge_train_ref?' do + subject { described_class.merge_train_ref?(ref) } + + context 'when ref is ref name of a branch' do + let(:ref) { 'feature' } + + it { is_expected.to be_falsey } + end + + context 'when ref is HEAD ref path of a branch' do + let(:ref) { 'refs/heads/feature' } + + it { is_expected.to be_falsey } + end + + context 'when ref is HEAD ref path of a merge request' do + let(:ref) { 'refs/merge-requests/1/head' } + + it { is_expected.to be_falsey } + end + + context 'when ref is merge ref path of a merge request' do + let(:ref) { 'refs/merge-requests/1/merge' } + + it { is_expected.to be_falsey } + end + + context 'when ref is train ref path of a merge request' do + let(:ref) { 'refs/merge-requests/1/train' } + + it { is_expected.to be_truthy } + end + end + describe '#cleanup_refs' do subject { merge_request.cleanup_refs(only: only) } diff --git a/spec/models/milestone_release_spec.rb b/spec/models/milestone_release_spec.rb new file mode 100644 index 00000000000..d6f73275977 --- /dev/null +++ b/spec/models/milestone_release_spec.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe MilestoneRelease do + let(:project) { create(:project) } + let(:release) { create(:release, project: project) } + let(:milestone) { create(:milestone, project: project) } + + subject { build(:milestone_release, release: release, milestone: milestone) } + + describe 'associations' do + it { is_expected.to belong_to(:milestone) } + it { is_expected.to belong_to(:release) } + end + + describe 'validations' do + it { is_expected.to validate_uniqueness_of(:milestone_id).scoped_to(:release_id) } + + context 'when milestone and release do not have the same project' do + it 'is not valid' do + other_project = create(:project) + release = build(:release, project: other_project) + milestone_release = described_class.new(milestone: milestone, release: release) + expect(milestone_release).not_to be_valid + end + end + + context 'when milestone and release have the same project' do + it 'is valid' do + milestone_release = described_class.new(milestone: milestone, release: release) + expect(milestone_release).to be_valid + end + end + end +end diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb index 3704a2d468d..64030f5b92a 100644 --- a/spec/models/milestone_spec.rb +++ b/spec/models/milestone_spec.rb @@ -54,11 +54,31 @@ describe Milestone do expect(milestone.errors[:due_date]).to include("date must not be after 9999-12-31") end end + + describe 'milestone_release' do + let(:milestone) { build(:milestone, project: project) } + + context 'when it is tied to a release for another project' do + it 'creates a validation error' do + other_project = create(:project) + milestone.release = build(:release, project: other_project) + expect(milestone).not_to be_valid + end + end + + context 'when it is tied to a release for the same project' do + it 'is valid' do + milestone.release = build(:release, project: project) + expect(milestone).to be_valid + end + end + end end describe "Associations" do it { is_expected.to belong_to(:project) } it { is_expected.to have_many(:issues) } + it { is_expected.to have_one(:release) } end let(:project) { create(:project, :public) } diff --git a/spec/models/release_spec.rb b/spec/models/release_spec.rb index e9d846e7291..29ce9f762b0 100644 --- a/spec/models/release_spec.rb +++ b/spec/models/release_spec.rb @@ -13,6 +13,7 @@ RSpec.describe Release do it { is_expected.to belong_to(:project) } it { is_expected.to belong_to(:author).class_name('User') } it { is_expected.to have_many(:links).class_name('Releases::Link') } + it { is_expected.to have_one(:milestone) } end describe 'validation' do @@ -34,6 +35,20 @@ RSpec.describe Release do expect(existing_release_without_name.name).to be_nil end end + + context 'when a release is tied to a milestone for another project' do + it 'creates a validation error' do + release.milestone = build(:milestone, project: create(:project)) + expect(release).not_to be_valid + end + end + + context 'when a release is tied to a milestone linked to the same project' do + it 'is valid' do + release.milestone = build(:milestone, project: project) + expect(release).to be_valid + end + end end describe '#assets_count' do diff --git a/spec/presenters/ci/pipeline_presenter_spec.rb b/spec/presenters/ci/pipeline_presenter_spec.rb index cda07a0ae09..7e8bbedcf6d 100644 --- a/spec/presenters/ci/pipeline_presenter_spec.rb +++ b/spec/presenters/ci/pipeline_presenter_spec.rb @@ -77,6 +77,40 @@ describe Ci::PipelinePresenter do end end + describe '#name' do + subject { presenter.name } + + context 'when pipeline is detached merge request pipeline' do + let(:merge_request) { create(:merge_request, :with_detached_merge_request_pipeline) } + let(:pipeline) { merge_request.all_pipelines.last } + + it { is_expected.to eq('Detached merge request pipeline') } + end + + context 'when pipeline is merge request pipeline' do + let(:merge_request) { create(:merge_request, :with_merge_request_pipeline) } + let(:pipeline) { merge_request.all_pipelines.last } + + it { is_expected.to eq('Merged result pipeline') } + end + + context 'when pipeline is merge train pipeline' do + let(:pipeline) { create(:ci_pipeline, project: project) } + + before do + allow(pipeline).to receive(:merge_request_event_type) { :merge_train } + end + + it { is_expected.to eq('Merge train pipeline') } + end + + context 'when pipeline is branch pipeline' do + let(:pipeline) { create(:ci_pipeline, project: project) } + + it { is_expected.to eq('Pipeline') } + end + end + describe '#ref_text' do subject { presenter.ref_text } diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 5465fe0c366..550c7d135a6 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -5,6 +5,8 @@ shared_examples 'languages and percentages JSON response' do let(:expected_languages) { project.repository.languages.map { |language| language.values_at(:label, :value)}.to_h } before do + allow(DetectRepositoryLanguagesWorker).to receive(:perform_async).and_call_original + allow(project.repository).to receive(:languages).and_return( [{ value: 66.69, label: "Ruby", color: "#701516", highlight: "#701516" }, { value: 22.98, label: "JavaScript", color: "#f1e05a", highlight: "#f1e05a" }, diff --git a/spec/serializers/pipeline_entity_spec.rb b/spec/serializers/pipeline_entity_spec.rb index 6be612ec226..eb9972d3e4d 100644 --- a/spec/serializers/pipeline_entity_spec.rb +++ b/spec/serializers/pipeline_entity_spec.rb @@ -41,7 +41,7 @@ describe PipelineEntity do it 'contains details' do expect(subject).to include :details expect(subject[:details]) - .to include :duration, :finished_at + .to include :duration, :finished_at, :name expect(subject[:details][:status]).to include :icon, :favicon, :text, :label, :tooltip end @@ -211,6 +211,10 @@ describe PipelineEntity do expect(subject[:source_sha]).to be_present expect(subject[:target_sha]).to be_present end + + it 'exposes merge request event type' do + expect(subject[:merge_request_event_type]).to be_present + end end end end diff --git a/spec/services/auth/container_registry_authentication_service_spec.rb b/spec/services/auth/container_registry_authentication_service_spec.rb index 3ca389ba25b..2807b8c8c85 100644 --- a/spec/services/auth/container_registry_authentication_service_spec.rb +++ b/spec/services/auth/container_registry_authentication_service_spec.rb @@ -476,7 +476,7 @@ describe Auth::ContainerRegistryAuthenticationService do let(:current_user) { create(:user) } let(:authentication_abilities) do - [:build_read_container_image, :build_create_container_image] + [:build_read_container_image, :build_create_container_image, :build_destroy_container_image] end before do @@ -507,19 +507,19 @@ describe Auth::ContainerRegistryAuthenticationService do end end - context 'disallow to delete images' do + context 'allow to delete images since registry 2.7' do let(:current_params) do - { scopes: ["repository:#{current_project.full_path}:*"] } + { scopes: ["repository:#{current_project.full_path}:delete"] } end - it_behaves_like 'an inaccessible' do + it_behaves_like 'a deletable since registry 2.7' do let(:project) { current_project } end end - context 'disallow to delete images since registry 2.7' do + context 'disallow to delete images' do let(:current_params) do - { scopes: ["repository:#{current_project.full_path}:delete"] } + { scopes: ["repository:#{current_project.full_path}:*"] } end it_behaves_like 'an inaccessible' do diff --git a/spec/services/milestones/destroy_service_spec.rb b/spec/services/milestones/destroy_service_spec.rb index 3a22e4d4f92..ff1e1256166 100644 --- a/spec/services/milestones/destroy_service_spec.rb +++ b/spec/services/milestones/destroy_service_spec.rb @@ -65,5 +65,19 @@ describe Milestones::DestroyService do expect { service.execute(group_milestone) }.not_to change { Event.count } end end + + context 'when a release is tied to a milestone' do + it 'destroys the milestone but not the associated release' do + release = create( + :release, + tag: 'v1.0', + project: project, + milestone: milestone + ) + + expect { service.execute(milestone) }.not_to change { Release.count } + expect(release.reload).to be_persisted + end + end end end diff --git a/spec/services/releases/create_service_spec.rb b/spec/services/releases/create_service_spec.rb index e26676cdd55..5c9d6537df1 100644 --- a/spec/services/releases/create_service_spec.rb +++ b/spec/services/releases/create_service_spec.rb @@ -72,6 +72,15 @@ describe Releases::CreateService do expect(project.releases.find_by(tag: tag_name).description).to eq(description) end end + + context 'when a passed-in milestone does not exist for this project' do + it 'raises an error saying the milestone is inexistent' do + service = described_class.new(project, user, params.merge!({ milestone: 'v111.0' })) + result = service.execute + expect(result[:status]).to eq(:error) + expect(result[:message]).to eq('Milestone does not exist') + end + end end describe '#find_or_build_release' do @@ -80,5 +89,58 @@ describe Releases::CreateService do expect(project.releases.count).to eq(0) end + + context 'when existing milestone is passed in' do + let(:title) { 'v1.0' } + let(:milestone) { create(:milestone, :active, project: project, title: title) } + let(:params_with_milestone) { params.merge!({ milestone: title }) } + + it 'creates a release and ties this milestone to it' do + service = described_class.new(milestone.project, user, params_with_milestone) + result = service.execute + + expect(project.releases.count).to eq(1) + expect(result[:status]).to eq(:success) + + release = project.releases.last + + expect(release.milestone).to eq(milestone) + end + + context 'when another release was previously created with that same milestone linked' do + it 'also creates another release tied to that same milestone' do + other_release = create(:release, milestone: milestone, project: project, tag: 'v1.0') + service = described_class.new(milestone.project, user, params_with_milestone) + service.execute + release = project.releases.last + + expect(release.milestone).to eq(milestone) + expect(other_release.milestone).to eq(milestone) + expect(release.id).not_to eq(other_release.id) + end + end + end + + context 'when no milestone is passed in' do + it 'creates a release without a milestone tied to it' do + expect(params.key? :milestone).to be_falsey + service.execute + release = project.releases.last + expect(release.milestone).to be_nil + end + + it 'does not create any new MilestoneRelease object' do + expect { service.execute }.not_to change { MilestoneRelease.count } + end + end + + context 'when an empty value is passed as a milestone' do + it 'creates a release without a milestone tied to it' do + service = described_class.new(project, user, params.merge!({ milestone: '' })) + service.execute + release = project.releases.last + expect(release.milestone).to be_nil + end + end end end diff --git a/spec/services/releases/destroy_service_spec.rb b/spec/services/releases/destroy_service_spec.rb index f4c901e6585..c3172e5edbc 100644 --- a/spec/services/releases/destroy_service_spec.rb +++ b/spec/services/releases/destroy_service_spec.rb @@ -57,5 +57,15 @@ describe Releases::DestroyService do http_status: 403) end end + + context 'when a milestone is tied to the release' do + let!(:milestone) { create(:milestone, :active, project: project, title: 'v1.0') } + let!(:release) { create(:release, milestone: milestone, project: project, tag: tag) } + + it 'destroys the release but leave the milestone intact' do + expect { subject }.not_to change { Milestone.count } + expect(milestone.reload).to be_persisted + end + end end end diff --git a/spec/services/releases/update_service_spec.rb b/spec/services/releases/update_service_spec.rb index 14e6a5f13c8..944f3d8c9ad 100644 --- a/spec/services/releases/update_service_spec.rb +++ b/spec/services/releases/update_service_spec.rb @@ -48,5 +48,42 @@ describe Releases::UpdateService do it_behaves_like 'a failed update' end + + context 'when a milestone is passed in' do + let(:old_title) { 'v1.0' } + let(:new_title) { 'v2.0' } + let(:milestone) { create(:milestone, project: project, title: old_title) } + let(:new_milestone) { create(:milestone, project: project, title: new_title) } + let(:params_with_milestone) { params.merge!({ milestone: new_title }) } + + before do + release.milestone = milestone + release.save! + + described_class.new(new_milestone.project, user, params_with_milestone).execute + release.reload + end + + it 'updates the related milestone accordingly' do + expect(release.milestone.title).to eq(new_title) + end + end + + context "when an 'empty' milestone is passed in" do + let(:milestone) { create(:milestone, project: project, title: 'v1.0') } + let(:params_with_empty_milestone) { params.merge!({ milestone: '' }) } + + before do + release.milestone = milestone + release.save! + + described_class.new(milestone.project, user, params_with_empty_milestone).execute + release.reload + end + + it 'removes the old milestone and does not associate any new milestone' do + expect(release.milestone).to be_nil + end + end end end diff --git a/spec/services/system_hooks_service_spec.rb b/spec/services/system_hooks_service_spec.rb index f5c6e972953..d72e5cc2b16 100644 --- a/spec/services/system_hooks_service_spec.rb +++ b/spec/services/system_hooks_service_spec.rb @@ -19,6 +19,7 @@ describe SystemHooksService do it { expect(event_data(project, :destroy)).to include(:event_name, :name, :created_at, :updated_at, :path, :project_id, :owner_name, :owner_email, :project_visibility) } it { expect(event_data(project_member, :create)).to include(:event_name, :created_at, :updated_at, :project_name, :project_path, :project_path_with_namespace, :project_id, :user_name, :user_username, :user_email, :user_id, :access_level, :project_visibility) } it { expect(event_data(project_member, :destroy)).to include(:event_name, :created_at, :updated_at, :project_name, :project_path, :project_path_with_namespace, :project_id, :user_name, :user_username, :user_email, :user_id, :access_level, :project_visibility) } + it { expect(event_data(project_member, :update)).to include(:event_name, :created_at, :updated_at, :project_name, :project_path, :project_path_with_namespace, :project_id, :user_name, :user_username, :user_email, :user_id, :access_level, :project_visibility) } it { expect(event_data(key, :create)).to include(:username, :key, :id) } it { expect(event_data(key, :destroy)).to include(:username, :key, :id) } it { expect(event_data(deploy_key, :create)).to include(:key, :id) } @@ -70,6 +71,13 @@ describe SystemHooksService do ) end + it do + expect(event_data(group_member, :update)).to include( + :event_name, :created_at, :updated_at, :group_name, :group_path, + :group_id, :user_id, :user_username, :user_name, :user_email, :group_access + ) + end + it 'includes the correct project visibility level' do data = event_data(project, :create) @@ -145,6 +153,7 @@ describe SystemHooksService do it { expect(event_name(project, :update)).to eq "project_update" } it { expect(event_name(project_member, :create)).to eq "user_add_to_team" } it { expect(event_name(project_member, :destroy)).to eq "user_remove_from_team" } + it { expect(event_name(project_member, :update)).to eq "user_update_for_team" } it { expect(event_name(key, :create)).to eq 'key_create' } it { expect(event_name(key, :destroy)).to eq 'key_destroy' } it { expect(event_name(group, :create)).to eq 'group_create' } @@ -152,6 +161,7 @@ describe SystemHooksService do it { expect(event_name(group, :rename)).to eq 'group_rename' } it { expect(event_name(group_member, :create)).to eq 'user_add_to_group' } it { expect(event_name(group_member, :destroy)).to eq 'user_remove_from_group' } + it { expect(event_name(group_member, :update)).to eq 'user_update_for_group' } end def event_data(*args) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index bd504f1553b..6ce76af556f 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -148,6 +148,12 @@ RSpec.configure do |config| .with(:force_autodevops_on_by_default, anything) .and_return(false) + # Stub this call due to being an expensive operation + # It can be reenabled for specific tests via: + # + # allow(DetectRepositoryLanguagesWorker).to receive(:perform_async).and_call_original + allow(DetectRepositoryLanguagesWorker).to receive(:perform_async).and_return(true) + Gitlab::ThreadMemoryCache.cache_backend.clear end diff --git a/spec/support/helpers/project_forks_helper.rb b/spec/support/helpers/project_forks_helper.rb index b2d22853e4c..90d0d1845fc 100644 --- a/spec/support/helpers/project_forks_helper.rb +++ b/spec/support/helpers/project_forks_helper.rb @@ -45,7 +45,7 @@ module ProjectForksHelper # not reset the @exists variable of this forked_project.repository # so we have to explicitly call this method to clear the @exists variable. # of the instance we're returning here. - forked_project.repository.after_import + forked_project.repository.expire_content_cache end forked_project diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb index a4acf76e1a3..8ca362ce2df 100644 --- a/spec/support/helpers/test_env.rb +++ b/spec/support/helpers/test_env.rb @@ -244,7 +244,6 @@ module TestEnv FileUtils.mkdir_p(target_repo_path) FileUtils.cp_r("#{File.expand_path(bare_repo)}/.", target_repo_path) FileUtils.chmod_R 0755, target_repo_path - set_repo_refs(target_repo_path, refs) end def create_bare_repository(path) |