Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
25bfb256b3
commit
71da5de44f
|
|
@ -980,59 +980,7 @@ Style/NumericPredicate:
|
|||
- 'ee/app/workers/geo/registry_sync_worker.rb'
|
||||
- 'ee/app/workers/geo/repository_shard_sync_worker.rb'
|
||||
- 'ee/app/workers/geo/repository_verification/primary/shard_worker.rb'
|
||||
- 'ee/lib/api/helpers/packages/conan/api_helpers.rb'
|
||||
- 'ee/lib/ee/gitlab/auth/ldap/person.rb'
|
||||
- 'ee/lib/ee/gitlab/background_migration/prune_orphaned_geo_events.rb'
|
||||
- 'ee/lib/ee/gitlab/checks/push_rules/file_size_check.rb'
|
||||
- 'ee/lib/ee/gitlab/geo_git_access.rb'
|
||||
- 'ee/lib/gitlab/geo/fdw.rb'
|
||||
- 'ee/lib/gitlab/geo/log_cursor/lease.rb'
|
||||
- 'ee/lib/tasks/gitlab/elastic.rake'
|
||||
- 'lib/api/entities/feature.rb'
|
||||
- 'lib/api/helpers/pagination_strategies.rb'
|
||||
- 'lib/backup/files.rb'
|
||||
- 'lib/banzai/filter/gollum_tags_filter.rb'
|
||||
- 'lib/bitbucket_server/paginator.rb'
|
||||
- 'lib/declarative_policy/runner.rb'
|
||||
- 'lib/gitlab/auth/ldap/adapter.rb'
|
||||
- 'lib/gitlab/bare_repository_import/importer.rb'
|
||||
- 'lib/gitlab/ci/config/external/context.rb'
|
||||
- 'lib/gitlab/ci/reports/accessibility_reports_comparer.rb'
|
||||
- 'lib/gitlab/cycle_analytics/summary/value.rb'
|
||||
- 'lib/gitlab/cycle_analytics/summary_helper.rb'
|
||||
- 'lib/gitlab/danger/teammate.rb'
|
||||
- 'lib/gitlab/database.rb'
|
||||
- 'lib/gitlab/database/connection_timer.rb'
|
||||
- 'lib/gitlab/database/migration_helpers.rb'
|
||||
- 'lib/gitlab/exclusive_lease.rb'
|
||||
- 'lib/gitlab/exclusive_lease_helpers/sleeping_lock.rb'
|
||||
- 'lib/gitlab/experimentation.rb'
|
||||
- 'lib/gitlab/file_hook.rb'
|
||||
- 'lib/gitlab/git/commit.rb'
|
||||
- 'lib/gitlab/git/repository.rb'
|
||||
- 'lib/gitlab/git/rugged_impl/blob.rb'
|
||||
- 'lib/gitlab/gitaly_client.rb'
|
||||
- 'lib/gitlab/github_import/user_finder.rb'
|
||||
- 'lib/gitlab/hashed_storage/migrator.rb'
|
||||
- 'lib/gitlab/import_export/command_line_util.rb'
|
||||
- 'lib/gitlab/multi_collection_paginator.rb'
|
||||
- 'lib/gitlab/polling_interval.rb'
|
||||
- 'lib/gitlab/project_search_results.rb'
|
||||
- 'lib/gitlab/seeder.rb'
|
||||
- 'lib/gitlab/sidekiq_cluster.rb'
|
||||
- 'lib/gitlab/sidekiq_daemon/memory_killer.rb'
|
||||
- 'lib/gitlab/sidekiq_middleware/memory_killer.rb'
|
||||
- 'lib/gitlab/sidekiq_status.rb'
|
||||
- 'lib/gitlab/slash_commands/presenters/issue_show.rb'
|
||||
- 'lib/gitlab/task_helpers.rb'
|
||||
- 'lib/gitlab/untrusted_regexp.rb'
|
||||
- 'lib/gitlab/utils.rb'
|
||||
- 'lib/system_check/sidekiq_check.rb'
|
||||
- 'lib/tasks/gitlab/gitaly.rake'
|
||||
- 'lib/tasks/gitlab/snippets.rake'
|
||||
- 'lib/tasks/gitlab/workhorse.rake'
|
||||
- 'ee/app/models/ee/project.rb'
|
||||
- 'lib/gitlab/usage_data/topology.rb'
|
||||
|
||||
# Offense count: 117
|
||||
# Cop supports --auto-correct.
|
||||
|
|
|
|||
|
|
@ -282,7 +282,7 @@ export default {
|
|||
<div
|
||||
v-if="showBoardListAndBoardInfo"
|
||||
class="issue-count-badge gl-display-inline-flex gl-pr-0 no-drag text-secondary"
|
||||
:class="{ 'gl-display-none': !list.isExpanded && isSwimlanesHeader }"
|
||||
:class="{ 'gl-display-none!': !list.isExpanded && isSwimlanesHeader }"
|
||||
>
|
||||
<span class="gl-display-inline-flex">
|
||||
<gl-tooltip :target="() => $refs.issueCount" :title="issuesTooltipLabel" />
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ export default {
|
|||
searchTerm: this.searchTerm,
|
||||
state: this.stateFilter,
|
||||
projectPath: this.projectPath,
|
||||
labelNames: ['incident'],
|
||||
issueTypes: ['INCIDENT'],
|
||||
firstPageSize: this.pagination.firstPageSize,
|
||||
lastPageSize: this.pagination.lastPageSize,
|
||||
prevPageCursor: this.pagination.prevPageCursor,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
query getIncidents(
|
||||
$projectPath: ID!
|
||||
$labelNames: [String]
|
||||
$issueTypes: [IssueType!]
|
||||
$state: IssuableState
|
||||
$firstPageSize: Int
|
||||
$lastPageSize: Int
|
||||
|
|
@ -12,7 +12,7 @@ query getIncidents(
|
|||
issues(
|
||||
search: $searchTerm
|
||||
state: $state
|
||||
labelName: $labelNames
|
||||
types: $issueTypes
|
||||
first: $firstPageSize
|
||||
last: $lastPageSize
|
||||
after: $nextPageCursor
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { GlAlert, GlButton, GlEmptyState, GlLink, GlSprintf } from '@gitlab/ui';
|
||||
import { GlAlert, GlButton, GlEmptyState, GlSprintf } from '@gitlab/ui';
|
||||
import { isEmpty } from 'lodash';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import { __ } from '~/locale';
|
||||
|
|
@ -23,7 +23,6 @@ export default {
|
|||
DagAnnotations,
|
||||
DagGraph,
|
||||
GlAlert,
|
||||
GlLink,
|
||||
GlSprintf,
|
||||
GlEmptyState,
|
||||
GlButton,
|
||||
|
|
@ -51,7 +50,6 @@ export default {
|
|||
failureType: null,
|
||||
graphData: null,
|
||||
showFailureAlert: false,
|
||||
showBetaInfo: true,
|
||||
hasNoDependentJobs: false,
|
||||
};
|
||||
},
|
||||
|
|
@ -72,11 +70,6 @@ export default {
|
|||
button: __('Learn more about job dependencies'),
|
||||
},
|
||||
computed: {
|
||||
betaMessage() {
|
||||
return __(
|
||||
'This feature is currently in beta. We invite you to %{linkStart}give feedback%{linkEnd}.',
|
||||
);
|
||||
},
|
||||
failure() {
|
||||
switch (this.failureType) {
|
||||
case LOAD_FAILURE:
|
||||
|
|
@ -154,9 +147,6 @@ export default {
|
|||
hideAlert() {
|
||||
this.showFailureAlert = false;
|
||||
},
|
||||
hideBetaInfo() {
|
||||
this.showBetaInfo = false;
|
||||
},
|
||||
removeAnnotationFromMap({ uid }) {
|
||||
this.$delete(this.annotationsMap, uid);
|
||||
},
|
||||
|
|
@ -188,15 +178,6 @@ export default {
|
|||
{{ failure.text }}
|
||||
</gl-alert>
|
||||
|
||||
<gl-alert v-if="showBetaInfo" @dismiss="hideBetaInfo">
|
||||
<gl-sprintf :message="betaMessage">
|
||||
<template #link="{ content }">
|
||||
<gl-link href="https://gitlab.com/gitlab-org/gitlab/-/issues/220368" target="_blank">
|
||||
{{ content }}
|
||||
</gl-link>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</gl-alert>
|
||||
<div class="gl-relative">
|
||||
<dag-annotations v-if="shouldDisplayAnnotations" :annotations="annotationsMap" />
|
||||
<dag-graph
|
||||
|
|
|
|||
|
|
@ -637,10 +637,13 @@ $issue-boards-card-shadow: rgba(0, 0, 0, 0.1);
|
|||
They probably should be derived in a smarter way.
|
||||
*/
|
||||
$issue-boards-filter-height: 68px;
|
||||
$issue-boards-filter-height-md: 110px;
|
||||
$issue-boards-filter-height-sm: 299px;
|
||||
$issue-boards-breadcrumbs-height-xs: 63px;
|
||||
$issue-board-list-difference-xs: $header-height + $issue-boards-breadcrumbs-height-xs;
|
||||
$issue-board-list-difference-sm: $header-height + $breadcrumb-min-height;
|
||||
$issue-board-list-difference-md: $issue-board-list-difference-sm + $issue-boards-filter-height;
|
||||
$issue-board-list-difference-md: $issue-board-list-difference-sm + $issue-boards-filter-height-md;
|
||||
$issue-board-list-difference-lg: $issue-board-list-difference-sm + $issue-boards-filter-height;
|
||||
/*
|
||||
The following heights are used in environment_logs.scss and are used for calculation of the log viewer height.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -59,6 +59,10 @@
|
|||
height: calc(100vh - #{$issue-board-list-difference-md});
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(lg) {
|
||||
height: calc(100vh - #{$issue-board-list-difference-lg});
|
||||
}
|
||||
|
||||
.with-performance-bar & {
|
||||
height: calc(100vh - #{$issue-board-list-difference-xs} - #{$performance-bar-height});
|
||||
|
||||
|
|
@ -69,6 +73,10 @@
|
|||
@include media-breakpoint-up(md) {
|
||||
height: calc(100vh - #{$issue-board-list-difference-md} - #{$performance-bar-height});
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(lg) {
|
||||
height: calc(100vh - #{$issue-board-list-difference-lg} - #{$performance-bar-height});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -191,7 +199,8 @@
|
|||
align-items: center;
|
||||
font-size: 1em;
|
||||
border-bottom: 1px solid $gray-100;
|
||||
padding: $gl-padding-8;
|
||||
padding: 0 $gl-spacing-scale-3;
|
||||
height: 3rem;
|
||||
|
||||
.js-max-issue-size::before {
|
||||
content: '/';
|
||||
|
|
@ -585,3 +594,21 @@
|
|||
.board-header-collapsed-info-icon:hover {
|
||||
color: $gray-900;
|
||||
}
|
||||
|
||||
$epic-icons-spacing: 40px;
|
||||
|
||||
.board-epic-lane {
|
||||
max-width: calc(100vw - #{$contextual-sidebar-width} - $epic-icons-spacing);
|
||||
|
||||
.page-with-icon-sidebar & {
|
||||
max-width: calc(100vw - #{$contextual-sidebar-collapsed-width} - $epic-icons-spacing);
|
||||
}
|
||||
|
||||
.page-with-icon-sidebar .is-compact & {
|
||||
max-width: calc(100vw - #{$contextual-sidebar-collapsed-width} - #{$gutter-width} - $epic-icons-spacing);
|
||||
}
|
||||
|
||||
.is-compact & {
|
||||
max-width: calc(100vw - #{$contextual-sidebar-width} - #{$gutter-width} - $epic-icons-spacing);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@
|
|||
|
||||
svg {
|
||||
position: relative;
|
||||
top: 5px;
|
||||
top: 3px;
|
||||
margin-right: 5px;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
|
|
@ -275,8 +275,6 @@
|
|||
overflow: auto;
|
||||
|
||||
svg {
|
||||
position: relative;
|
||||
top: 3px;
|
||||
margin-right: 3px;
|
||||
height: 14px;
|
||||
width: 14px;
|
||||
|
|
|
|||
|
|
@ -183,7 +183,7 @@
|
|||
|
||||
.option-description,
|
||||
.option-disabled-reason {
|
||||
margin-left: 30px;
|
||||
margin-left: 20px;
|
||||
color: $project-option-descr-color;
|
||||
margin-top: -5px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,13 @@ module Projects
|
|||
|
||||
def panel_preview
|
||||
respond_to do |format|
|
||||
format.json { render json: render_panel }
|
||||
format.json do
|
||||
if rendered_panel.success?
|
||||
render json: rendered_panel.payload
|
||||
else
|
||||
render json: { message: rendered_panel.message }, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -19,25 +25,21 @@ module Projects
|
|||
render_404 unless Feature.enabled?(:metrics_dashboard_new_panel_page, project)
|
||||
end
|
||||
|
||||
def render_panel
|
||||
{
|
||||
"title": "Memory Usage (Total)",
|
||||
"type": "area-chart",
|
||||
"y_label": "Total Memory Used (GB)",
|
||||
"weight": 4,
|
||||
"metrics": [
|
||||
{
|
||||
"id": "system_metrics_kubernetes_container_memory_total",
|
||||
"query_range": "avg(sum(container_memory_usage_bytes{container_name!=\"POD\",pod_name=~\"^{{ci_environment_slug}}-(.*)\",namespace=\"{{kube_namespace}}\"}) by (job)) without (job) /1024/1024/1024",
|
||||
"label": "Total (GB)",
|
||||
"unit": "GB",
|
||||
"metric_id": 15,
|
||||
"edit_path": nil,
|
||||
"prometheus_endpoint_path": "/root/autodevops-deploy/-/environments/29/prometheus/api/v1/query_range?query=avg%28sum%28container_memory_usage_bytes%7Bcontainer_name%21%3D%22POD%22%2Cpod_name%3D~%22%5E%7B%7Bci_environment_slug%7D%7D-%28.%2A%29%22%2Cnamespace%3D%22%7B%7Bkube_namespace%7D%7D%22%7D%29+by+%28job%29%29+without+%28job%29++%2F1024%2F1024%2F1024"
|
||||
}
|
||||
],
|
||||
"id": "4570deed516d0bf93fb42879004117009ab456ced27393ec8dce5b6960438132"
|
||||
}
|
||||
def rendered_panel
|
||||
@panel_preview ||= ::Metrics::Dashboard::PanelPreviewService.new(project, panel_yaml, environment).execute
|
||||
end
|
||||
|
||||
def panel_yaml
|
||||
params.require(:panel_yaml)
|
||||
end
|
||||
|
||||
def environment
|
||||
@environment ||=
|
||||
if params[:environment]
|
||||
project.environments.find(params[:environment])
|
||||
else
|
||||
project.default_environment
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -22,7 +22,9 @@ module DesignManagement
|
|||
items = by_filename(items)
|
||||
items = by_id(items)
|
||||
|
||||
items
|
||||
# TODO: We don't need to pass the project anymore after the feature flag is removed
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/issues/34382
|
||||
items.ordered(issue.project)
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
|||
|
|
@ -42,15 +42,15 @@ class ApplicationRecord < ActiveRecord::Base
|
|||
limit(count)
|
||||
end
|
||||
|
||||
def self.safe_find_or_create_by!(*args)
|
||||
safe_find_or_create_by(*args).tap do |record|
|
||||
def self.safe_find_or_create_by!(*args, &block)
|
||||
safe_find_or_create_by(*args, &block).tap do |record|
|
||||
record.validate! unless record.persisted?
|
||||
end
|
||||
end
|
||||
|
||||
def self.safe_find_or_create_by(*args)
|
||||
def self.safe_find_or_create_by(*args, &block)
|
||||
safe_ensure_unique(retries: 1) do
|
||||
find_or_create_by(*args)
|
||||
find_or_create_by(*args, &block)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ module DesignManagement
|
|||
include Referable
|
||||
include Mentionable
|
||||
include WhereComposite
|
||||
include RelativePositioning
|
||||
|
||||
belongs_to :project, inverse_of: :designs
|
||||
belongs_to :issue
|
||||
|
|
@ -75,7 +76,19 @@ module DesignManagement
|
|||
join = designs.join(actions)
|
||||
.on(actions[:design_id].eq(designs[:id]))
|
||||
|
||||
joins(join.join_sources).where(actions[:event].not_eq(deletion)).order(:id)
|
||||
joins(join.join_sources).where(actions[:event].not_eq(deletion))
|
||||
end
|
||||
|
||||
scope :ordered, -> (project) do
|
||||
# TODO: Always order by relative position after the feature flag is removed
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/issues/34382
|
||||
if Feature.enabled?(:reorder_designs, project)
|
||||
# We need to additionally sort by `id` to support keyset pagination.
|
||||
# See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/17788/diffs#note_230875678
|
||||
order(:relative_position, :id)
|
||||
else
|
||||
order(:id)
|
||||
end
|
||||
end
|
||||
|
||||
scope :with_filename, -> (filenames) { where(filename: filenames) }
|
||||
|
|
@ -87,6 +100,14 @@ module DesignManagement
|
|||
# A design is current if the most recent event is not a deletion
|
||||
scope :current, -> { visible_at_version(nil) }
|
||||
|
||||
def self.relative_positioning_query_base(design)
|
||||
on_issue(design.issue_id)
|
||||
end
|
||||
|
||||
def self.relative_positioning_parent_column
|
||||
:issue_id
|
||||
end
|
||||
|
||||
def status
|
||||
if new_design?
|
||||
:new
|
||||
|
|
|
|||
|
|
@ -12,7 +12,9 @@ module DesignManagement
|
|||
|
||||
def find_or_create_design!(filename:)
|
||||
designs.find { |design| design.filename == filename } ||
|
||||
designs.safe_find_or_create_by!(project: project, filename: filename)
|
||||
designs.safe_find_or_create_by!(project: project, filename: filename) do |design|
|
||||
design.move_to_end
|
||||
end
|
||||
end
|
||||
|
||||
def versions
|
||||
|
|
|
|||
|
|
@ -0,0 +1,54 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Ingest YAML fragment with metrics dashboard panel definition
|
||||
# https://docs.gitlab.com/ee/operations/metrics/dashboards/yaml.html#panel-panels-properties
|
||||
# process it and returns renderable json version
|
||||
module Metrics
|
||||
module Dashboard
|
||||
class PanelPreviewService
|
||||
SEQUENCE = [
|
||||
::Gitlab::Metrics::Dashboard::Stages::CommonMetricsInserter,
|
||||
::Gitlab::Metrics::Dashboard::Stages::MetricEndpointInserter,
|
||||
::Gitlab::Metrics::Dashboard::Stages::PanelIdsInserter,
|
||||
::Gitlab::Metrics::Dashboard::Stages::AlertsInserter,
|
||||
::Gitlab::Metrics::Dashboard::Stages::UrlValidator
|
||||
].freeze
|
||||
|
||||
HANDLED_PROCESSING_ERRORS = [
|
||||
Gitlab::Metrics::Dashboard::Errors::DashboardProcessingError,
|
||||
Gitlab::Config::Loader::Yaml::NotHashError,
|
||||
Gitlab::Config::Loader::Yaml::DataTooLargeError,
|
||||
Gitlab::Config::Loader::FormatError
|
||||
].freeze
|
||||
|
||||
def initialize(project, panel_yaml, environment)
|
||||
@project, @panel_yaml, @environment = project, panel_yaml, environment
|
||||
end
|
||||
|
||||
def execute
|
||||
dashboard = ::Gitlab::Metrics::Dashboard::Processor.new(project, dashboard_structure, SEQUENCE, environment: environment).process
|
||||
ServiceResponse.success(payload: dashboard[:panel_groups][0][:panels][0])
|
||||
rescue *HANDLED_PROCESSING_ERRORS => error
|
||||
ServiceResponse.error(message: error.message)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_accessor :project, :panel_yaml, :environment
|
||||
|
||||
def dashboard_structure
|
||||
{
|
||||
panel_groups: [
|
||||
{
|
||||
panels: [panel_hash]
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
def panel_hash
|
||||
::Gitlab::Config::Loader::Yaml.new(panel_yaml).load_raw!
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -8,13 +8,13 @@
|
|||
= expanded ? _('Collapse') : _('Expand')
|
||||
|
||||
%p
|
||||
= html_escape(_('Environment variables are applied to all project environments in this instance via the Runner. You can use environment variables for passwords, secret keys, etc. Make variables available to the running application by prepending the variable key with %{code_open}K8S_SECRET_%{code_close}. You can set variables to be:')) % { code_open: '<code>'.html_safe, code_close: '</code>'.html_safe }
|
||||
= html_escape(_('Environment variables are applied to environments via the Runner. You can use environment variables for passwords, secret keys, etc. Make variables available to the running application by prepending the variable key with %{code_open}K8S_SECRET_%{code_close}. You can set variables to be:')) % { code_open: '<code>'.html_safe, code_close: '</code>'.html_safe }
|
||||
|
||||
%ul
|
||||
%li
|
||||
= html_escape(_('%{code_open}Protected%{code_close} to expose them to protected branches or tags only.')) % { code_open: '<code>'.html_safe, code_close: '</code>'.html_safe }
|
||||
= html_escape(_('%{code_open}Protected%{code_close} variables are only exposed to protected branches or tags.')) % { code_open: '<code>'.html_safe, code_close: '</code>'.html_safe }
|
||||
%li
|
||||
= html_escape(_('%{code_open}Masked%{code_close} to prevent the values from being displayed in job logs (must match certain regexp requirements).')) % { code_open: '<code>'.html_safe, code_close: '</code>'.html_safe }
|
||||
= html_escape(_('%{code_open}Masked%{code_close} variables are hidden in job logs (though they must match certain regexp requirements to do so).')) % { code_open: '<code>'.html_safe, code_close: '</code>'.html_safe }
|
||||
|
||||
%p
|
||||
= link_to _('More information'), help_page_path('ci/variables/README', anchor: 'instance-level-cicd-environment-variables')
|
||||
|
|
|
|||
|
|
@ -1,3 +1,8 @@
|
|||
= _('Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they can be masked so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want.')
|
||||
= html_escape(_('You may also add variables that are made available to the running application by prepending the variable key with %{k8s_secret}.')) % { k8s_secret: tag.code('K8S_SECRET_') }
|
||||
= html_escape(_('Environment variables are applied to environments via the Runner. You can use environment variables for passwords, secret keys, etc. Make variables available to the running application by prepending the variable key with %{code_open}K8S_SECRET_%{code_close}. You can set variables to be:')) % { code_open: '<code>'.html_safe, code_close: '</code>'.html_safe }
|
||||
%ul
|
||||
%li
|
||||
= html_escape(_('%{code_open}Protected%{code_close} variables are only exposed to protected branches or tags.')) % { code_open: '<code>'.html_safe, code_close: '</code>'.html_safe }
|
||||
%li
|
||||
= html_escape(_('%{code_open}Masked%{code_close} variables are hidden in job logs (though they must match certain regexp requirements to do so).')) % { code_open: '<code>'.html_safe, code_close: '</code>'.html_safe }
|
||||
|
||||
= link_to _('More information'), help_page_path('ci/variables/README', anchor: 'custom-environment-variables')
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@
|
|||
%li.js-dag-tab-link
|
||||
= link_to dag_project_pipeline_path(@project, @pipeline), data: { target: '#js-tab-dag', action: 'dag', toggle: 'tab' }, class: 'dag-tab' do
|
||||
= _('DAG')
|
||||
%span.badge-pill.gl-badge.sm.gl-bg-blue-500.gl-text-white.gl-ml-2= _('Beta')
|
||||
%li.js-builds-tab-link
|
||||
= link_to builds_project_pipeline_path(@project, @pipeline), data: { target: '#js-tab-builds', action: 'builds', toggle: 'tab' }, class: 'builds-tab' do
|
||||
= _('Jobs')
|
||||
|
|
|
|||
|
|
@ -173,7 +173,7 @@
|
|||
= render 'shared/issuable/board_create_list_dropdown', board: board
|
||||
- if @project
|
||||
#js-add-issues-btn.gl-ml-3{ data: { can_admin_list: can?(current_user, :admin_list, @project) } }
|
||||
- if Feature.enabled?(:boards_with_swimlanes, @group)
|
||||
- if current_user && Feature.enabled?(:boards_with_swimlanes, @group)
|
||||
#js-board-epics-swimlanes-toggle
|
||||
#js-toggle-focus-btn
|
||||
- elsif is_not_boards_modal_or_productivity_analytics && show_sorting_dropdown
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Order projects within the project dropdown by relevance in analytics features
|
||||
merge_request: 38675
|
||||
author:
|
||||
type: changed
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add relative positioning on designs
|
||||
merge_request: 37835
|
||||
author:
|
||||
type: changed
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Take DAG view out of beta
|
||||
merge_request: 38517
|
||||
author:
|
||||
type: changed
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix vertical alignment of svg icons on Jobs page
|
||||
merge_request: 38656
|
||||
author:
|
||||
type: fixed
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Button migration vulnerability charts
|
||||
merge_request: 38610
|
||||
author:
|
||||
type: changed
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
name: reorder_designs
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/37835
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/232992
|
||||
group: group::knowledge
|
||||
type: development
|
||||
default_enabled: false
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddRelativePositionToDesignManagementDesigns < ActiveRecord::Migration[6.0]
|
||||
DOWNTIME = false
|
||||
|
||||
def change
|
||||
add_column :design_management_designs, :relative_position, :integer
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddIndexOnDesignManagementDesignsIssueIdAndRelativePositionAndId < ActiveRecord::Migration[6.0]
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
DOWNTIME = false
|
||||
INDEX_NAME = 'index_design_management_designs_issue_id_relative_position_id'
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
add_concurrent_index :design_management_designs, [:issue_id, :relative_position, :id], name: INDEX_NAME
|
||||
end
|
||||
|
||||
def down
|
||||
remove_concurrent_index :design_management_designs, [:issue_id, :relative_position, :id], name: INDEX_NAME
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
d4e389b1469b968b703432de9ece6c362e45ec4ba3ad20d1f6c6418253969379
|
||||
|
|
@ -0,0 +1 @@
|
|||
a16e7fdcc62f39af3038317cb39ffb4c35f41ae45f5de429f18837309739110b
|
||||
|
|
@ -11203,7 +11203,8 @@ CREATE TABLE public.design_management_designs (
|
|||
id bigint NOT NULL,
|
||||
project_id integer NOT NULL,
|
||||
issue_id integer,
|
||||
filename character varying NOT NULL
|
||||
filename character varying NOT NULL,
|
||||
relative_position integer
|
||||
);
|
||||
|
||||
CREATE SEQUENCE public.design_management_designs_id_seq
|
||||
|
|
@ -19394,6 +19395,8 @@ CREATE INDEX index_description_versions_on_issue_id ON public.description_versio
|
|||
|
||||
CREATE INDEX index_description_versions_on_merge_request_id ON public.description_versions USING btree (merge_request_id) WHERE (merge_request_id IS NOT NULL);
|
||||
|
||||
CREATE INDEX index_design_management_designs_issue_id_relative_position_id ON public.design_management_designs USING btree (issue_id, relative_position, id);
|
||||
|
||||
CREATE UNIQUE INDEX index_design_management_designs_on_issue_id_and_filename ON public.design_management_designs USING btree (issue_id, filename);
|
||||
|
||||
CREATE INDEX index_design_management_designs_on_project_id ON public.design_management_designs USING btree (project_id);
|
||||
|
|
|
|||
|
|
@ -2264,6 +2264,51 @@ enum DastScanTypeEnum {
|
|||
PASSIVE
|
||||
}
|
||||
|
||||
"""
|
||||
Represents a DAST scanner profile.
|
||||
"""
|
||||
type DastScannerProfile {
|
||||
"""
|
||||
ID of the DAST scanner profile
|
||||
"""
|
||||
id: ID!
|
||||
|
||||
"""
|
||||
Name of the DAST scanner profile
|
||||
"""
|
||||
profileName: String
|
||||
|
||||
"""
|
||||
The maximum number of seconds allowed for the spider to traverse the site
|
||||
"""
|
||||
spiderTimeout: Int
|
||||
|
||||
"""
|
||||
The maximum number of seconds allowed for the site under test to respond to a request
|
||||
"""
|
||||
targetTimeout: Int
|
||||
}
|
||||
|
||||
"""
|
||||
The connection type for DastScannerProfile.
|
||||
"""
|
||||
type DastScannerProfileConnection {
|
||||
"""
|
||||
A list of edges.
|
||||
"""
|
||||
edges: [DastScannerProfileEdge]
|
||||
|
||||
"""
|
||||
A list of nodes.
|
||||
"""
|
||||
nodes: [DastScannerProfile]
|
||||
|
||||
"""
|
||||
Information to aid in pagination.
|
||||
"""
|
||||
pageInfo: PageInfo!
|
||||
}
|
||||
|
||||
"""
|
||||
Autogenerated input type of DastScannerProfileCreate
|
||||
"""
|
||||
|
|
@ -2314,6 +2359,21 @@ type DastScannerProfileCreatePayload {
|
|||
id: ID
|
||||
}
|
||||
|
||||
"""
|
||||
An edge in a connection.
|
||||
"""
|
||||
type DastScannerProfileEdge {
|
||||
"""
|
||||
A cursor for use in pagination.
|
||||
"""
|
||||
cursor: String!
|
||||
|
||||
"""
|
||||
The item at the end of the edge.
|
||||
"""
|
||||
node: DastScannerProfile
|
||||
}
|
||||
|
||||
"""
|
||||
Represents a DAST Site Profile.
|
||||
"""
|
||||
|
|
@ -9687,6 +9747,31 @@ type Project {
|
|||
"""
|
||||
createdAt: Time
|
||||
|
||||
"""
|
||||
The DAST scanner profiles associated with the project
|
||||
"""
|
||||
dastScannerProfiles(
|
||||
"""
|
||||
Returns the elements in the list that come after the specified cursor.
|
||||
"""
|
||||
after: String
|
||||
|
||||
"""
|
||||
Returns the elements in the list that come before the specified cursor.
|
||||
"""
|
||||
before: String
|
||||
|
||||
"""
|
||||
Returns the first _n_ elements from the list.
|
||||
"""
|
||||
first: Int
|
||||
|
||||
"""
|
||||
Returns the last _n_ elements from the list.
|
||||
"""
|
||||
last: Int
|
||||
): DastScannerProfileConnection
|
||||
|
||||
"""
|
||||
DAST Site Profiles associated with the project
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -6078,6 +6078,146 @@
|
|||
],
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "DastScannerProfile",
|
||||
"description": "Represents a DAST scanner profile.",
|
||||
"fields": [
|
||||
{
|
||||
"name": "id",
|
||||
"description": "ID of the DAST scanner profile",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "ID",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "profileName",
|
||||
"description": "Name of the DAST scanner profile",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "spiderTimeout",
|
||||
"description": "The maximum number of seconds allowed for the spider to traverse the site",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Int",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "targetTimeout",
|
||||
"description": "The maximum number of seconds allowed for the site under test to respond to a request",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Int",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [
|
||||
|
||||
],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "DastScannerProfileConnection",
|
||||
"description": "The connection type for DastScannerProfile.",
|
||||
"fields": [
|
||||
{
|
||||
"name": "edges",
|
||||
"description": "A list of edges.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "LIST",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "DastScannerProfileEdge",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "nodes",
|
||||
"description": "A list of nodes.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "LIST",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "DastScannerProfile",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "pageInfo",
|
||||
"description": "Information to aid in pagination.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "OBJECT",
|
||||
"name": "PageInfo",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [
|
||||
|
||||
],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "DastScannerProfileCreateInput",
|
||||
|
|
@ -6214,6 +6354,51 @@
|
|||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "DastScannerProfileEdge",
|
||||
"description": "An edge in a connection.",
|
||||
"fields": [
|
||||
{
|
||||
"name": "cursor",
|
||||
"description": "A cursor for use in pagination.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "node",
|
||||
"description": "The item at the end of the edge.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "OBJECT",
|
||||
"name": "DastScannerProfile",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [
|
||||
|
||||
],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "DastSiteProfile",
|
||||
|
|
@ -28993,6 +29178,59 @@
|
|||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "dastScannerProfiles",
|
||||
"description": "The DAST scanner profiles associated with the project",
|
||||
"args": [
|
||||
{
|
||||
"name": "after",
|
||||
"description": "Returns the elements in the list that come after the specified cursor.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "before",
|
||||
"description": "Returns the elements in the list that come before the specified cursor.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "first",
|
||||
"description": "Returns the first _n_ elements from the list.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Int",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "last",
|
||||
"description": "Returns the last _n_ elements from the list.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Int",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
}
|
||||
],
|
||||
"type": {
|
||||
"kind": "OBJECT",
|
||||
"name": "DastScannerProfileConnection",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "dastSiteProfiles",
|
||||
"description": "DAST Site Profiles associated with the project",
|
||||
|
|
|
|||
|
|
@ -392,6 +392,17 @@ Autogenerated return type of DastOnDemandScanCreate
|
|||
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
|
||||
| `pipelineUrl` | String | URL of the pipeline that was created. |
|
||||
|
||||
## DastScannerProfile
|
||||
|
||||
Represents a DAST scanner profile.
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | ---- | ---------- |
|
||||
| `id` | ID! | ID of the DAST scanner profile |
|
||||
| `profileName` | String | Name of the DAST scanner profile |
|
||||
| `spiderTimeout` | Int | The maximum number of seconds allowed for the spider to traverse the site |
|
||||
| `targetTimeout` | Int | The maximum number of seconds allowed for the site under test to respond to a request |
|
||||
|
||||
## DastScannerProfileCreatePayload
|
||||
|
||||
Autogenerated return type of DastScannerProfileCreate
|
||||
|
|
|
|||
|
|
@ -84,6 +84,7 @@ are certain use cases that you may need to work around. For more information:
|
|||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/215517) in GitLab 13.1 as a [Beta feature](https://about.gitlab.com/handbook/product/#beta).
|
||||
> - It was deployed behind a feature flag, disabled by default.
|
||||
> - It became [enabled by default](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/36802) in 13.2.
|
||||
> - It became a [standard feature](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/38517) in 13.3.
|
||||
> - It's enabled on GitLab.com.
|
||||
> - For GitLab self-managed instances, GitLab administrators can opt to [disable it](#enable-or-disable-dag-visualization-core-only).
|
||||
|
||||
|
|
@ -97,9 +98,7 @@ Clicking a node will highlight all the job paths it depends on.
|
|||
|
||||
### Enable or disable DAG Visualization **(CORE ONLY)**
|
||||
|
||||
DAG Visualization is under development, but is being made available as a beta feature so users can check its limitations and uses.
|
||||
|
||||
It is deployed behind a feature flag that is **enabled by default**.
|
||||
DAG Visualization is deployed behind a feature flag that is **enabled by default**.
|
||||
[GitLab administrators with access to the GitLab Rails console](../../administration/feature_flags.md)
|
||||
can opt to disable it for your instance:
|
||||
|
||||
|
|
|
|||
|
|
@ -1415,6 +1415,9 @@ In this example:
|
|||
to continue running even if the job is not triggered (`allow_failure: true`).
|
||||
- If `Dockerfile` has not changed, do not add job to any pipeline (same as `when: never`).
|
||||
|
||||
To implement a rule similar to [`except: changes`](#onlychangesexceptchanges),
|
||||
use `when: never`.
|
||||
|
||||
##### `rules:exists`
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/24021) in GitLab 12.4.
|
||||
|
|
@ -2227,6 +2230,9 @@ failure.
|
|||
[manual actions](#whenmanual) below.
|
||||
1. `delayed` - execute job after a certain period (added in GitLab 11.14).
|
||||
Read about [delayed actions](#whendelayed) below.
|
||||
1. `never`:
|
||||
- With [`rules`](#rules), don't execute job.
|
||||
- With [`workflow:rules`](#workflowrules), don't run pipeline.
|
||||
|
||||
For example:
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,9 @@
|
|||
---
|
||||
stage: Configure
|
||||
group: Configure
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
|
||||
---
|
||||
|
||||
# Auto DevOps development guide
|
||||
|
||||
This document provides a development guide for contributors to
|
||||
|
|
|
|||
|
|
@ -1,3 +1,9 @@
|
|||
---
|
||||
stage: Configure
|
||||
group: Configure
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
|
||||
---
|
||||
|
||||
# Chatops on GitLab.com
|
||||
|
||||
ChatOps on GitLab.com allows GitLab team members to run various automation tasks on GitLab.com using Slack.
|
||||
|
|
|
|||
|
|
@ -1,3 +1,9 @@
|
|||
---
|
||||
stage: Configure
|
||||
group: Configure
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
|
||||
---
|
||||
|
||||
# Dependency Management in Go
|
||||
|
||||
Go takes an unusual approach to dependency management, in that it is
|
||||
|
|
|
|||
|
|
@ -1,3 +1,9 @@
|
|||
---
|
||||
stage: Configure
|
||||
group: Configure
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
|
||||
---
|
||||
|
||||
# Go standards and style guidelines
|
||||
|
||||
This document describes various guidelines and best practices for GitLab
|
||||
|
|
|
|||
|
|
@ -1,3 +1,9 @@
|
|||
---
|
||||
stage: Configure
|
||||
group: Configure
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
|
||||
---
|
||||
|
||||
# Kubernetes integration - development guidelines
|
||||
|
||||
This document provides various guidelines when developing for GitLab's
|
||||
|
|
|
|||
|
|
@ -8,13 +8,106 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
|
||||
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/2479) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.10.
|
||||
|
||||
GitLab Status Page allows you to create and deploy a static website to communicate efficiently to users during an incident.
|
||||
With a GitLab Status Page, you can create and deploy a static website to communicate
|
||||
efficiently to users during an incident. The Status Page landing page displays an
|
||||
overview of recent incidents:
|
||||
|
||||
## How to set up
|
||||

|
||||
|
||||
Clicking an incident displays a detail page with more information about a particular incident:
|
||||
|
||||

|
||||
|
||||
- Status on the incident, including when the incident was last updated.
|
||||
- The incident title, including any emojis.
|
||||
- The description of the incident, including emojis.
|
||||
- Any file attachments provided in the incident description, or comments with a
|
||||
valid image extension. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/205166) in GitLab 13.1.
|
||||
- A chronological ordered list of updates to the incident.
|
||||
|
||||
## Set up a GitLab Status Page
|
||||
|
||||
To configure a GitLab Status Page you must:
|
||||
|
||||
1. [Configure GitLab](#configure-gitlab-with-cloud-provider-information) with your
|
||||
cloud provider information.
|
||||
1. [Configure your AWS account](#configure-your-aws-account).
|
||||
1. [Create a Status Page project](#create-a-status-page-project) on GitLab.
|
||||
1. [Sync incidents to the Status Page](#sync-incidents-to-the-status-page).
|
||||
|
||||
### Configure GitLab with cloud provider information
|
||||
|
||||
To provide GitLab with the AWS account information needed to push content to your Status Page:
|
||||
|
||||
NOTE: **Note:**
|
||||
Only AWS S3 is supported as a deploy target.
|
||||
|
||||
1. Sign into GitLab as a user with Maintainer or greater [permissions](../../user/permissions.md).
|
||||
1. Navigate to **{settings}** **Settings > Operations**. Next to **Status Page**,
|
||||
click **Expand**.
|
||||
1. Click **Active** to enable the Status Page feature.
|
||||
1. In **Status Page URL**, provide the URL to your external status page.
|
||||
1. Provide the **S3 Bucket name**. For more information, see
|
||||
[Bucket configuration documentation](https://docs.aws.amazon.com/AmazonS3/latest/dev/HostingWebsiteOnS3Setup.html).
|
||||
1. Provide the **AWS region** for your bucket. For more information, see the
|
||||
[AWS documentation](https://github.com/aws/aws-sdk-ruby#configuration).
|
||||
1. Provide your **AWS access key ID** and **AWS Secret access key**.
|
||||
1. Click **Save changes**.
|
||||
|
||||
### Configure your AWS account
|
||||
|
||||
1. Within your AWS account, create two new IAM policies, using the following files
|
||||
as examples:
|
||||
- [Create bucket](https://gitlab.com/gitlab-org/status-page/-/blob/master/deploy/etc/s3_create_policy.json).
|
||||
- [Update bucket contents](https://gitlab.com/gitlab-org/status-page/-/blob/master/deploy/etc/s3_update_bucket_policy.json) (Remember replace `S3_BUCKET_NAME` with your bucket name).
|
||||
1. Create a new AWS access key with the permissions policies created in the first step.
|
||||
|
||||
### Create a status page project
|
||||
|
||||
After configuring your AWS account, you must add the Status Page project and configure
|
||||
the necessary CI/CD variables to deploy the Status Page to AWS S3:
|
||||
|
||||
1. Fork the [Status Page](https://gitlab.com/gitlab-org/status-page) project.
|
||||
You can do this through [Repository Mirroring](https://gitlab.com/gitlab-org/status-page#repository-mirroring),
|
||||
which ensures you get the up-to-date Status Page features.
|
||||
1. Navigate to **{settings}** **Settings > CI/CD**.
|
||||
1. Scroll to **Variables**, and click **Expand**.
|
||||
1. Add the following variables from your Amazon Console:
|
||||
- `S3_BUCKET_NAME` - The name of the Amazon S3 bucket.
|
||||
|
||||
NOTE: **Note:**
|
||||
If no bucket with the provided name exists, the first pipeline run creates
|
||||
one and configures it for
|
||||
[static website hosting](https://docs.aws.amazon.com/AmazonS3/latest/dev/HostingWebsiteOnS3Setup.html).
|
||||
|
||||
- `AWS_DEFAULT_REGION` - The AWS region.
|
||||
- `AWS_ACCESS_KEY_ID` - The AWS access key ID.
|
||||
- `AWS_SECRET_ACCESS_KEY` - The AWS secret.
|
||||
1. Navigate to **CI / CD > Pipelines > Run Pipeline**, and run the pipeline to
|
||||
deploy the Status Page to S3.
|
||||
|
||||
CAUTION: **Caution:**
|
||||
Consider limiting who can access issues in this project, as any user who can view
|
||||
the issue can potentially [publish comments to your GitLab Status Page](#publish-comments-on-incidents).
|
||||
|
||||
### Sync incidents to the Status Page
|
||||
|
||||
After creating the CI/CD variables, configure the Project you want to use for
|
||||
Incident issues:
|
||||
|
||||
1. To view the [Operations Settings](../../user/project/settings/#operations-settings)
|
||||
page, navigate to **{settings}** **Settings > Operations > Status Page**.
|
||||
1. Fill in your cloud provider's credentials and make sure the **Active** checkbox is checked.
|
||||
1. Click **Save changes**.
|
||||
|
||||
## How to use your GitLab Status Page
|
||||
|
||||
After configuring your GitLab instance, relevant updates trigger a background job
|
||||
that pushes JSON-formatted data about the incident to your external cloud provider.
|
||||
Your status page website periodically fetches this JSON-formatted data. It formats
|
||||
and displays it to users, providing information about ongoing incidents without
|
||||
extra effort from your team:
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph GitLab Instance
|
||||
|
|
@ -28,107 +121,59 @@ graph TB
|
|||
end
|
||||
```
|
||||
|
||||
Setting up a Status Page is pretty painless but there are a few things you need to do.
|
||||
### Publish an incident
|
||||
|
||||
### Cloud account set up
|
||||
To publish an incident:
|
||||
|
||||
To use GitLab Status Page you first need to set up your account details for your cloud provider in the operations settings page. Today, only AWS is supported.
|
||||
1. Create an issue in the project you enabled the GitLab Status Page settings in.
|
||||
1. A [project or group owner](../../user/permissions.md) must use the
|
||||
`/publish` [quick action](../../user/project/quick_actions.md) to publish the
|
||||
issue to the GitLab Status Page.
|
||||
|
||||
#### AWS Setup
|
||||
NOTE: **Note:**
|
||||
Confidential issues can't be published.
|
||||
|
||||
1. Within your AWS acccout, create two new IAM policies.
|
||||
- [Create bucket](https://gitlab.com/gitlab-org/status-page/-/blob/master/deploy/etc/s3_create_policy.json).
|
||||
- [Update bucket contents](https://gitlab.com/gitlab-org/status-page/-/blob/master/deploy/etc/s3_update_bucket_policy.json) (Remember replace `S3_BUCKET_NAME` with your bucket name).
|
||||
1. Create a new AWS access key with the permissions policies created in the first step.
|
||||
A background worker publishes the issue onto the Status Page using the credentials
|
||||
you provided during setup. As part of publication, GitLab will:
|
||||
|
||||
### Status Page project
|
||||
- Anonymize user and group mentions with `Incident Responder`.
|
||||
- Remove titles of non-public [GitLab references](../../user/markdown.md#special-gitlab-references).
|
||||
- Publish any files attached to incident issue descriptions, up to 5000 per issue.
|
||||
([Introduced in GitLab 13.1](https://gitlab.com/gitlab-org/gitlab/-/issues/205166).)
|
||||
|
||||
To deploy the Status Page to AWS S3 you need to add the Status Page project & configure the necessary CI variables.
|
||||
|
||||
1. Fork the [Status Page](https://gitlab.com/gitlab-org/status-page) project. This can also be done via [Repository Mirroring](https://gitlab.com/gitlab-org/status-page#repository-mirroring) which will ensure you get the up-to-date Status Page features.
|
||||
1. Add the following variables in **Settings > CI/CD > Variables**. (To get these variables from Amazon, use your Amazon Console):
|
||||
- `S3_BUCKET_NAME` - name of the Amazon S3 bucket (If a bucket with the provided name doesn't exist, the first pipeline run will create one and configure it for [static website hosting](https://docs.aws.amazon.com/AmazonS3/latest/dev/HostingWebsiteOnS3Setup.html))
|
||||
- `AWS_DEFAULT_REGION` - the AWS region
|
||||
- `AWS_ACCESS_KEY_ID` - the AWS access key ID
|
||||
- `AWS_SECRET_ACCESS_KEY` - the AWS secret
|
||||
1. Run the pipeline to deploy the Status Page to S3.
|
||||
|
||||
### Syncing incidents to the Status Page
|
||||
|
||||
Once the CI/CD variables are set, you'll need to set up the Project you want to use for Incident issues:
|
||||
|
||||
1. To view the [Operations Settings](../../user/project/settings/#operations-settings) page, navigate to **Settings > Operations > Status Page**.
|
||||
1. Fill in your cloud provider's credentials and make sure the **Active** checkbox is checked.
|
||||
1. Click **Save changes**.
|
||||
|
||||
## Status Page UI
|
||||
|
||||
The Status Page landing page shows you an overview of the recent incidents. Clicking on an incident will take you to the incident's detail page.
|
||||
|
||||

|
||||
|
||||
### Incident detail page
|
||||
|
||||
The incident detail page shows detailed information about a particular incident. For example:
|
||||
|
||||
- Status on the incident, including when the incident was last updated.
|
||||
- The incident title, including any emojis.
|
||||
- The description of the incident, including emojis.
|
||||
- Any file attachments provided in the incident description or comments with a valid image extension. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/205166) in GitLab 13.1.
|
||||
- A chronological ordered list of updates to the incident.
|
||||
|
||||

|
||||
|
||||
## How it works
|
||||
|
||||
### Publishing Incidents
|
||||
|
||||
To publish an Incident, you first need to create an issue in the Project you enabled the Status Page settings in.
|
||||
|
||||
Issues are not published to the Status Page by default. Use the `/publish` [quick action](../../user/project/quick_actions.md) in an issue to publish the issue. Only [project or group owners](../../user/permissions.md) are permitted to publish issues.
|
||||
|
||||
After the quick action is used, a background worker publishes the issue onto the Status Page using the credentials you provided during setup.
|
||||
|
||||
Since all incidents are published publicly, user and group mentions are anonymized with `Incident Responder`,
|
||||
and titles of non-public [GitLab references](../../user/markdown.md#special-gitlab-references) are removed.
|
||||
|
||||
When an Incident is published in the GitLab project, you can access the
|
||||
details page of the Incident by clicking the **Published on status page** button
|
||||
displayed under the Incident's title.
|
||||
After publication, you can access the incident's details page by clicking the
|
||||
**Published on status page** button displayed under the Incident's title.
|
||||
|
||||

|
||||
|
||||
NOTE: **Note:**
|
||||
Confidential issues can't be published. If you make a published issue confidential, it will be unpublished.
|
||||
|
||||
### Publishing updates
|
||||
### Update an incident
|
||||
|
||||
To publish an update to the Incident, update the incident issue's description.
|
||||
|
||||
CAUTION: **Caution:**
|
||||
When referenced issues are changed (e.g. title, confidentiality) the incident they were referenced in are not updated automatically.
|
||||
When referenced issues are changed (such as title or confidentiality) the incident
|
||||
they were referenced in is not updated.
|
||||
|
||||
### Adding comments
|
||||
### Publish comments on incidents
|
||||
|
||||
To add comments to the Status Page Incident, create a comment on the incident issue.
|
||||
To publish comments to the Status Page Incident:
|
||||
|
||||
When you're ready to publish the comment, add a microphone [award emoji](../../user/award_emojis.md) reaction (`:microphone` 🎤) to the comment. This marks the comment as one which should be deployed to the Status Page.
|
||||
- Create a comment on the incident issue.
|
||||
- When you're ready to publish the comment, mark the comment for publication by
|
||||
adding a microphone [award emoji](../../user/award_emojis.md)
|
||||
reaction (`:microphone:` 🎤) to the comment.
|
||||
- Any files attached to the comment (up to 5000 per issue) are also published.
|
||||
([Introduced in GitLab 13.1](https://gitlab.com/gitlab-org/gitlab/-/issues/205166).)
|
||||
|
||||
CAUTION: **Caution:**
|
||||
Anyone with access to view the Issue can add an Emoji Award to a comment, so you may want to keep your Issues limited to team members only.
|
||||
Anyone with access to view the Issue can add an emoji award to a comment, so
|
||||
consider limiting access to issues to team members only.
|
||||
|
||||
### Changing the Incident status
|
||||
### Update the incident status
|
||||
|
||||
To change the incident status from `open` to `closed`, close the incident issue within GitLab. This will then be updated shortly on the Status Page website.
|
||||
To change the incident status from `open` to `closed`, close the incident issue
|
||||
within GitLab. Closing the issue triggers a background worker to update the
|
||||
GitLab Status Page website.
|
||||
|
||||
## Attachment storage
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/205166) in GitLab 13.1.
|
||||
|
||||
Beginning with GitLab 13.1, files attached to incident issue descriptions or
|
||||
comments are published and unpublished to the status page storage as part of
|
||||
the [publication flow](#how-it-works).
|
||||
|
||||
### Limit
|
||||
|
||||
Only 5000 attachments per issue will be transferred to the status page.
|
||||
If you make a published issue confidential, GitLab unpublishes it from your
|
||||
GitLab Status Page website.
|
||||
|
|
|
|||
|
|
@ -65,7 +65,8 @@ to add an issue to an epic, reorder issues, move issues between epics, or promot
|
|||
|
||||
## Issue health status in Epic tree **(ULTIMATE)**
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/199184) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.10.
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/199184) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.10.
|
||||
> - [Improved](https://gitlab.com/gitlab-org/gitlab/-/issues/220867) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 13.3. The health status of a closed issue will be hidden.
|
||||
|
||||
You can report on and quickly respond to the health of individual issues and epics by setting a
|
||||
red, amber, or green [health status on an issue](../../project/issues/index.md#health-status-ultimate),
|
||||
|
|
|
|||
|
|
@ -285,5 +285,5 @@ Add the URL of a Jaeger server to allow your users to [easily access the Jaeger
|
|||
|
||||
### Status Page
|
||||
|
||||
[Add Storage credentials](../../../operations/incident_management/status_page.md#syncing-incidents-to-the-status-page)
|
||||
to enable the syncing of public Issues to a [deployed status page](../../../operations/incident_management/status_page.md#status-page-project).
|
||||
[Add Storage credentials](../../../operations/incident_management/status_page.md#sync-incidents-to-the-status-page)
|
||||
to enable the syncing of public Issues to a [deployed status page](../../../operations/incident_management/status_page.md#create-a-status-page-project).
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ module API
|
|||
value = model.gate_values[gate.key]
|
||||
|
||||
# By default all gate values are populated. Only show relevant ones.
|
||||
if (value.is_a?(Integer) && value.zero?) || (value.is_a?(Set) && value.empty?)
|
||||
if (value.is_a?(Integer) && value == 0) || (value.is_a?(Set) && value.empty?)
|
||||
next
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ module API
|
|||
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
def handle_similarity_order(group, projects)
|
||||
if params[:search].present? && Feature.enabled?(:similarity_search, group)
|
||||
if params[:search].present? && Feature.enabled?(:similarity_search, group, default_enabled: true)
|
||||
projects.sorted_by_similarity_desc(params[:search])
|
||||
else
|
||||
order_options = { name: :asc }
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ module API
|
|||
end
|
||||
|
||||
def offset_limit_exceeded?(offset_limit)
|
||||
offset_limit.positive? && params[:page] * params[:per_page] > offset_limit
|
||||
offset_limit > 0 && params[:page] * params[:per_page] > offset_limit
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -56,16 +56,20 @@ module API
|
|||
end
|
||||
params do
|
||||
requires :title, type: String, allow_blank: false, desc: 'The title of the snippet'
|
||||
requires :file_name, type: String, desc: 'The file name of the snippet'
|
||||
requires :content, type: String, allow_blank: false, desc: 'The content of the snippet'
|
||||
optional :description, type: String, desc: 'The description of a snippet'
|
||||
requires :visibility, type: String,
|
||||
values: Gitlab::VisibilityLevel.string_values,
|
||||
desc: 'The visibility of the snippet'
|
||||
use :create_file_params
|
||||
end
|
||||
post ":id/snippets" do
|
||||
authorize! :create_snippet, user_project
|
||||
snippet_params = declared_params(include_missing: false).merge(request: request, api: true)
|
||||
snippet_params = declared_params(include_missing: false).tap do |create_args|
|
||||
create_args[:request] = request
|
||||
create_args[:api] = true
|
||||
|
||||
process_file_args(create_args)
|
||||
end
|
||||
|
||||
service_response = ::Snippets::CreateService.new(user_project, current_user, snippet_params).execute
|
||||
snippet = service_response.payload[:snippet]
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ module Backup
|
|||
cmd = %W(rsync -a --exclude=lost+found #{app_files_dir} #{Gitlab.config.backup.path})
|
||||
output, status = Gitlab::Popen.popen(cmd)
|
||||
|
||||
unless status.zero?
|
||||
unless status == 0
|
||||
puts output
|
||||
raise Backup::Error, 'Backup failed'
|
||||
end
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ module Banzai
|
|||
def process_tag(tag)
|
||||
parts = tag.split('|')
|
||||
|
||||
return if parts.size.zero?
|
||||
return if parts.empty?
|
||||
|
||||
process_image_tag(parts) || process_page_link_tag(parts)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ module BitbucketServer
|
|||
def over_limit?
|
||||
return false unless @limit
|
||||
|
||||
@limit.positive? && @total >= @limit
|
||||
@limit > 0 && @total >= @limit
|
||||
end
|
||||
|
||||
def next_offset
|
||||
|
|
|
|||
|
|
@ -170,7 +170,7 @@ module DeclarativePolicy
|
|||
lowest_score = score
|
||||
end
|
||||
|
||||
break if lowest_score.zero?
|
||||
break if lowest_score == 0
|
||||
end
|
||||
|
||||
[remaining_steps, remaining_enablers, remaining_preventers].each do |set|
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ module Gitlab
|
|||
if results.nil?
|
||||
response = ldap.get_operation_result
|
||||
|
||||
unless response.code.zero?
|
||||
unless response.code == 0
|
||||
Rails.logger.warn("LDAP search error: #{response.message}") # rubocop:disable Gitlab/RailsLogger
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@ module Gitlab
|
|||
cmd = %W(#{Gitlab.config.git.bin_path} --git-dir=#{repo_path} bundle create #{bundle_path} --all)
|
||||
output, status = Gitlab::Popen.popen(cmd)
|
||||
|
||||
raise output unless status.zero?
|
||||
raise output unless status == 0
|
||||
|
||||
bundle_path
|
||||
end
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def execution_expired?
|
||||
return false if execution_deadline.zero?
|
||||
return false if execution_deadline == 0
|
||||
|
||||
current_monotonic_time > execution_deadline
|
||||
end
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def status
|
||||
head_reports.errors_count.positive? ? STATUS_FAILED : STATUS_SUCCESS
|
||||
head_reports.errors_count > 0 ? STATUS_FAILED : STATUS_SUCCESS
|
||||
end
|
||||
|
||||
def existing_errors
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def to_s
|
||||
value.zero? ? '0' : value.to_s
|
||||
value == 0 ? '0' : value.to_s
|
||||
end
|
||||
|
||||
def to_i
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ module Gitlab
|
|||
module CycleAnalytics
|
||||
module SummaryHelper
|
||||
def frequency(count, from, to)
|
||||
return Summary::Value::None.new if count.zero?
|
||||
return Summary::Value::None.new if count == 0
|
||||
|
||||
freq = (count / days(from, to)).round(1)
|
||||
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ module Gitlab
|
|||
|
||||
def offset_diff_compared_to_author(author)
|
||||
diff = floored_offset_hours - author.floored_offset_hours
|
||||
return "same timezone as `@#{author.username}`" if diff.zero?
|
||||
return "same timezone as `@#{author.username}`" if diff == 0
|
||||
|
||||
ahead_or_behind = diff < 0 ? 'behind' : 'ahead of'
|
||||
pluralized_hours = pluralize(diff.abs, 'hour', 'hours')
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ module Gitlab
|
|||
|
||||
# @deprecated
|
||||
def self.postgresql?
|
||||
adapter_name.casecmp('postgresql').zero?
|
||||
adapter_name.casecmp('postgresql') == 0
|
||||
end
|
||||
|
||||
def self.read_only?
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def interval_with_randomization
|
||||
interval + rand(RANDOMIZATION_INTERVAL) if interval.positive?
|
||||
interval + rand(RANDOMIZATION_INTERVAL) if interval > 0
|
||||
end
|
||||
|
||||
def current_clock_value
|
||||
|
|
|
|||
|
|
@ -1062,7 +1062,7 @@ into similar problems in the future (e.g. when new tables are created).
|
|||
AND pg_class.relname = '#{table}'
|
||||
SQL
|
||||
|
||||
connection.select_value(check_sql).positive?
|
||||
connection.select_value(check_sql) > 0
|
||||
end
|
||||
|
||||
# Adds a check constraint to a table
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ module Gitlab
|
|||
Gitlab::Redis::SharedState.with do |redis|
|
||||
ttl = redis.ttl(@redis_shared_state_key)
|
||||
|
||||
ttl if ttl.positive?
|
||||
ttl if ttl > 0
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def first_attempt?
|
||||
attempts.zero?
|
||||
attempts == 0
|
||||
end
|
||||
|
||||
def sleep_sec
|
||||
|
|
|
|||
|
|
@ -170,7 +170,7 @@ module Gitlab
|
|||
|
||||
Experiment = Struct.new(:key, :environment, :tracking_category, keyword_init: true) do
|
||||
def enabled?
|
||||
experiment_percentage.positive?
|
||||
experiment_percentage > 0
|
||||
end
|
||||
|
||||
def enabled_for_environment?
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
exit_status = result.status&.exitstatus
|
||||
[exit_status.zero?, result.stderr]
|
||||
[exit_status == 0, result.stderr]
|
||||
rescue => e
|
||||
[false, e.message]
|
||||
end
|
||||
|
|
|
|||
|
|
@ -261,7 +261,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def has_zero_stats?
|
||||
stats.total.zero?
|
||||
stats.total == 0
|
||||
rescue
|
||||
true
|
||||
end
|
||||
|
|
@ -423,7 +423,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def message_from_gitaly_body
|
||||
return @raw_commit.subject.dup if @raw_commit.body_size.zero?
|
||||
return @raw_commit.subject.dup if @raw_commit.body_size == 0
|
||||
return @raw_commit.body.dup if full_body_fetched_from_gitaly?
|
||||
|
||||
if @raw_commit.body_size > MAX_COMMIT_MESSAGE_DISPLAY_SIZE
|
||||
|
|
|
|||
|
|
@ -815,7 +815,7 @@ module Gitlab
|
|||
def fsck
|
||||
msg, status = gitaly_repository_client.fsck
|
||||
|
||||
raise GitError.new("Could not fsck repository: #{msg}") unless status.zero?
|
||||
raise GitError.new("Could not fsck repository: #{msg}") unless status == 0
|
||||
end
|
||||
|
||||
def create_from_bundle(bundle_path)
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ module Gitlab
|
|||
name: blob_entry[:name],
|
||||
size: blob.size,
|
||||
# Rugged::Blob#content is expensive; don't call it if we don't have to.
|
||||
data: limit.zero? ? '' : blob.content(limit),
|
||||
data: limit == 0 ? '' : blob.content(limit),
|
||||
mode: blob_entry[:filemode].to_s(8),
|
||||
path: path,
|
||||
commit_id: sha,
|
||||
|
|
|
|||
|
|
@ -476,7 +476,7 @@ module Gitlab
|
|||
return unless stack_counter
|
||||
|
||||
max = max_call_count
|
||||
return if max.zero?
|
||||
return if max == 0
|
||||
|
||||
stack_counter.select { |_, v| v == max }.keys
|
||||
end
|
||||
|
|
|
|||
|
|
@ -161,7 +161,7 @@ module Gitlab
|
|||
|
||||
# The cache key may be empty to indicate a previously looked up user for
|
||||
# which we couldn't find an ID.
|
||||
[exists, number.positive? ? number : nil]
|
||||
[exists, number > 0 ? number : nil]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ module Gitlab
|
|||
|
||||
def any_non_empty_queue?(*workers)
|
||||
workers.any? do |worker|
|
||||
!Sidekiq::Queue.new(worker.queue).size.zero?
|
||||
Sidekiq::Queue.new(worker.queue).size != 0 # rubocop:disable Style/ZeroLengthPredicate
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -47,8 +47,8 @@ module Gitlab
|
|||
|
||||
def execute(cmd)
|
||||
output, status = Gitlab::Popen.popen(cmd)
|
||||
@shared.error(Gitlab::ImportExport::Error.new(output.to_s)) unless status.zero? # rubocop:disable Gitlab/ModuleWithInstanceVariables
|
||||
status.zero?
|
||||
@shared.error(Gitlab::ImportExport::Error.new(output.to_s)) unless status == 0 # rubocop:disable Gitlab/ModuleWithInstanceVariables
|
||||
status == 0
|
||||
end
|
||||
|
||||
def git_bin_path
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ module Gitlab
|
|||
@second_collection_pages ||= Hash.new do |hash, page|
|
||||
second_collection_page = page - first_collection_page_count
|
||||
|
||||
offset = if second_collection_page < 1 || first_collection_page_count.zero?
|
||||
offset = if second_collection_page < 1 || first_collection_page_count == 0
|
||||
0
|
||||
else
|
||||
per_page - first_collection_last_page_size
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def self.polling_enabled?
|
||||
!Gitlab::CurrentSettings.polling_interval_multiplier.zero?
|
||||
Gitlab::CurrentSettings.polling_interval_multiplier != 0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ module Gitlab
|
|||
counts = %i(limited_milestones_count limited_notes_count
|
||||
limited_merge_requests_count limited_issues_count
|
||||
limited_blobs_count wiki_blobs_count)
|
||||
counts.all? { |count_method| public_send(count_method).zero? } # rubocop:disable GitlabSecurity/PublicSend
|
||||
counts.all? { |count_method| public_send(count_method) == 0 } # rubocop:disable GitlabSecurity/PublicSend
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ module Gitlab
|
|||
estimated_minutes = (size.to_f / ESTIMATED_INSERT_PER_MINUTE).round
|
||||
humanized_minutes = 'minute'.pluralize(estimated_minutes)
|
||||
|
||||
if estimated_minutes.zero?
|
||||
if estimated_minutes == 0
|
||||
"Rough estimated time: less than a minute ⏰"
|
||||
else
|
||||
"Rough estimated time: #{estimated_minutes} #{humanized_minutes} ⏰"
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ module Gitlab
|
|||
|
||||
def self.concurrency(queues, min_concurrency, max_concurrency)
|
||||
concurrency_from_queues = queues.length + 1
|
||||
max = max_concurrency.positive? ? max_concurrency : concurrency_from_queues
|
||||
max = max_concurrency > 0 ? max_concurrency : concurrency_from_queues
|
||||
min = [min_concurrency, max].min
|
||||
|
||||
concurrency_from_queues.clamp(min, max)
|
||||
|
|
|
|||
|
|
@ -239,7 +239,7 @@ module Gitlab
|
|||
memory_growth_kb = get_job_options(job, 'memory_killer_memory_growth_kb', 0).to_i
|
||||
max_memory_growth_kb = get_job_options(job, 'memory_killer_max_memory_growth_kb', DEFAULT_MAX_MEMORY_GROWTH_KB).to_i
|
||||
|
||||
return 0 if memory_growth_kb.zero?
|
||||
return 0 if memory_growth_kb == 0
|
||||
|
||||
time_elapsed = [Gitlab::Metrics::System.monotonic_time - job[:started_at], 0].max
|
||||
[memory_growth_kb * time_elapsed, max_memory_growth_kb].min
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ module Gitlab
|
|||
|
||||
def get_rss
|
||||
output, status = Gitlab::Popen.popen(%W(ps -o rss= -p #{pid}), Rails.root.to_s)
|
||||
return 0 unless status.zero?
|
||||
return 0 unless status == 0
|
||||
|
||||
output.to_i
|
||||
end
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ module Gitlab
|
|||
#
|
||||
# Returns true or false.
|
||||
def self.all_completed?(job_ids)
|
||||
self.num_running(job_ids).zero?
|
||||
self.num_running(job_ids) == 0
|
||||
end
|
||||
|
||||
# Returns true if the given job is running or enqueued.
|
||||
|
|
|
|||
|
|
@ -23,14 +23,14 @@ module Gitlab
|
|||
def text
|
||||
message = ["**#{status_text(resource)}**"]
|
||||
|
||||
if resource.upvotes.zero? && resource.downvotes.zero? && resource.user_notes_count.zero?
|
||||
if resource.upvotes == 0 && resource.downvotes == 0 && resource.user_notes_count == 0
|
||||
return message.join
|
||||
end
|
||||
|
||||
message << " · "
|
||||
message << ":+1: #{resource.upvotes} " unless resource.upvotes.zero?
|
||||
message << ":-1: #{resource.downvotes} " unless resource.downvotes.zero?
|
||||
message << ":speech_balloon: #{resource.user_notes_count}" unless resource.user_notes_count.zero?
|
||||
message << ":+1: #{resource.upvotes} " unless resource.upvotes == 0
|
||||
message << ":-1: #{resource.downvotes} " unless resource.downvotes == 0
|
||||
message << ":speech_balloon: #{resource.user_notes_count}" unless resource.user_notes_count == 0
|
||||
|
||||
message.join
|
||||
end
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ module Gitlab
|
|||
def run_command!(command)
|
||||
output, status = Gitlab::Popen.popen(command)
|
||||
|
||||
raise Gitlab::TaskFailedError.new(output) unless status.zero?
|
||||
raise Gitlab::TaskFailedError.new(output) unless status == 0
|
||||
|
||||
output
|
||||
end
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ module Gitlab
|
|||
|
||||
def scan(text)
|
||||
matches = scan_regexp.scan(text).to_a
|
||||
matches.map!(&:first) if regexp.number_of_capturing_groups.zero?
|
||||
matches.map!(&:first) if regexp.number_of_capturing_groups == 0
|
||||
matches
|
||||
end
|
||||
|
||||
|
|
@ -68,7 +68,7 @@ module Gitlab
|
|||
# groups, so work around it
|
||||
def scan_regexp
|
||||
@scan_regexp ||=
|
||||
if regexp.number_of_capturing_groups.zero?
|
||||
if regexp.number_of_capturing_groups == 0
|
||||
RE2::Regexp.new('(' + regexp.source + ')')
|
||||
else
|
||||
regexp
|
||||
|
|
|
|||
|
|
@ -213,7 +213,7 @@ module Gitlab
|
|||
|
||||
def normalize_localhost_address(instance)
|
||||
ip_addr = IPAddr.new(instance)
|
||||
is_local_ip = ip_addr.loopback? || ip_addr.to_i.zero?
|
||||
is_local_ip = ip_addr.loopback? || ip_addr.to_i == 0
|
||||
|
||||
is_local_ip ? 'localhost' : instance
|
||||
rescue IPAddr::InvalidAddressError
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ module Gitlab
|
|||
|
||||
def ensure_utf8_size(str, bytes:)
|
||||
raise ArgumentError, 'Empty string provided!' if str.empty?
|
||||
raise ArgumentError, 'Negative string size provided!' if bytes.negative?
|
||||
raise ArgumentError, 'Negative string size provided!' if bytes < 0
|
||||
|
||||
truncated = str.each_char.each_with_object(+'') do |char, object|
|
||||
if object.bytesize + char.bytesize > bytes
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ module SystemCheck
|
|||
|
||||
def only_one_sidekiq_running
|
||||
process_count = sidekiq_process_count
|
||||
return if process_count.zero?
|
||||
return if process_count == 0
|
||||
|
||||
$stdout.print 'Number of Sidekiq processes ... '
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ Usage: rake "gitlab:gitaly:install[/installation/dir,/storage/path]")
|
|||
|
||||
command = []
|
||||
_, status = Gitlab::Popen.popen(%w[which gmake])
|
||||
command << (status.zero? ? 'gmake' : 'make')
|
||||
command << (status == 0 ? 'gmake' : 'make')
|
||||
|
||||
if Rails.env.test?
|
||||
command.push(
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ namespace :gitlab do
|
|||
raise "Please supply the list of ids through the SNIPPET_IDS env var"
|
||||
end
|
||||
|
||||
raise "Invalid limit value" if limit.zero?
|
||||
raise "Invalid limit value" if limit == 0
|
||||
|
||||
if migration_running?
|
||||
raise "There are already snippet migrations running. Please wait until they are finished."
|
||||
|
|
@ -37,7 +37,7 @@ namespace :gitlab do
|
|||
def parse_snippet_ids!
|
||||
ids = ENV['SNIPPET_IDS'].delete(' ').split(',').map do |id|
|
||||
id.to_i.tap do |value|
|
||||
raise "Invalid id provided" if value.zero?
|
||||
raise "Invalid id provided" if value == 0
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -68,10 +68,10 @@ namespace :gitlab do
|
|||
# bundle exec rake gitlab:snippets:list_non_migrated LIMIT=50
|
||||
desc 'GitLab | Show non migrated snippets'
|
||||
task list_non_migrated: :environment do
|
||||
raise "Invalid limit value" if limit.zero?
|
||||
raise "Invalid limit value" if limit == 0
|
||||
|
||||
non_migrated_count = non_migrated_snippets.count
|
||||
if non_migrated_count.zero?
|
||||
if non_migrated_count == 0
|
||||
puts "All snippets have been successfully migrated"
|
||||
else
|
||||
puts "There are #{non_migrated_count} snippets that haven't been migrated. Showing a batch of ids of those snippets:\n"
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ namespace :gitlab do
|
|||
checkout_or_clone_version(version: version, repo: args.repo, target_dir: args.dir, clone_opts: %w[--depth 1])
|
||||
|
||||
_, status = Gitlab::Popen.popen(%w[which gmake])
|
||||
command = status.zero? ? 'gmake' : 'make'
|
||||
command = status == 0 ? 'gmake' : 'make'
|
||||
|
||||
Dir.chdir(args.dir) do
|
||||
run_command!([command])
|
||||
|
|
|
|||
|
|
@ -315,10 +315,10 @@ msgstr ""
|
|||
msgid "%{code_open}\"johnsmith@example.com\": \"johnsmith@example.com\"%{code_close} will add \"By %{link_open}johnsmith@example.com%{link_close}\" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address."
|
||||
msgstr ""
|
||||
|
||||
msgid "%{code_open}Masked%{code_close} to prevent the values from being displayed in job logs (must match certain regexp requirements)."
|
||||
msgid "%{code_open}Masked%{code_close} variables are hidden in job logs (though they must match certain regexp requirements to do so)."
|
||||
msgstr ""
|
||||
|
||||
msgid "%{code_open}Protected%{code_close} to expose them to protected branches or tags only."
|
||||
msgid "%{code_open}Protected%{code_close} variables are only exposed to protected branches or tags."
|
||||
msgstr ""
|
||||
|
||||
msgid "%{commit_author_link} authored %{commit_timeago}"
|
||||
|
|
@ -3764,9 +3764,6 @@ msgstr ""
|
|||
msgid "Below you will find all the groups that are public."
|
||||
msgstr ""
|
||||
|
||||
msgid "Beta"
|
||||
msgstr ""
|
||||
|
||||
msgid "Bi-weekly code coverage"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -9174,10 +9171,7 @@ msgstr ""
|
|||
msgid "Environment scope"
|
||||
msgstr ""
|
||||
|
||||
msgid "Environment variables are applied to all project environments in this instance via the Runner. You can use environment variables for passwords, secret keys, etc. Make variables available to the running application by prepending the variable key with %{code_open}K8S_SECRET_%{code_close}. You can set variables to be:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. Additionally, they can be masked so they are hidden in job logs, though they must match certain regexp requirements to do so. You can use environment variables for passwords, secret keys, or whatever you want."
|
||||
msgid "Environment variables are applied to environments via the Runner. You can use environment variables for passwords, secret keys, etc. Make variables available to the running application by prepending the variable key with %{code_open}K8S_SECRET_%{code_close}. You can set variables to be:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
|
||||
|
|
@ -24637,9 +24631,6 @@ msgstr ""
|
|||
msgid "This epic does not exist or you don't have sufficient permission."
|
||||
msgstr ""
|
||||
|
||||
msgid "This feature is currently in beta. We invite you to %{linkStart}give feedback%{linkEnd}."
|
||||
msgstr ""
|
||||
|
||||
msgid "This feature requires local storage to be enabled"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -27782,9 +27773,6 @@ msgstr ""
|
|||
msgid "You left the \"%{membershipable_human_name}\" %{source_type}."
|
||||
msgstr ""
|
||||
|
||||
msgid "You may also add variables that are made available to the running application by prepending the variable key with %{k8s_secret}."
|
||||
msgstr ""
|
||||
|
||||
msgid "You may close the milestone now."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@ RSpec.describe DesignManagement::DesignsFinder do
|
|||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:project) { create(:project, :private) }
|
||||
let_it_be(:issue) { create(:issue, project: project) }
|
||||
let_it_be(:design1) { create(:design, :with_file, issue: issue, versions_count: 1) }
|
||||
let_it_be(:design2) { create(:design, :with_file, issue: issue, versions_count: 1) }
|
||||
let_it_be(:design3) { create(:design, :with_file, issue: issue, versions_count: 1) }
|
||||
let_it_be(:design1) { create(:design, :with_file, issue: issue, versions_count: 1, relative_position: 3) }
|
||||
let_it_be(:design2) { create(:design, :with_file, issue: issue, versions_count: 1, relative_position: 2) }
|
||||
let_it_be(:design3) { create(:design, :with_file, issue: issue, versions_count: 1, relative_position: 1) }
|
||||
let(:params) { {} }
|
||||
|
||||
subject(:designs) { described_class.new(issue, user, params).execute }
|
||||
|
|
@ -38,8 +38,28 @@ RSpec.describe DesignManagement::DesignsFinder do
|
|||
enable_design_management
|
||||
end
|
||||
|
||||
it 'returns the designs' do
|
||||
is_expected.to contain_exactly(design1, design2, design3)
|
||||
it 'returns the designs sorted by their relative position' do
|
||||
is_expected.to eq([design3, design2, design1])
|
||||
end
|
||||
|
||||
context 'when the :reorder_designs feature is enabled for the project' do
|
||||
before do
|
||||
stub_feature_flags(reorder_designs: project)
|
||||
end
|
||||
|
||||
it 'returns the designs sorted by their relative position' do
|
||||
is_expected.to eq([design3, design2, design1])
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the :reorder_designs feature is disabled' do
|
||||
before do
|
||||
stub_feature_flags(reorder_designs: false)
|
||||
end
|
||||
|
||||
it 'returns the designs sorted by ID' do
|
||||
is_expected.to eq([design1, design2, design3])
|
||||
end
|
||||
end
|
||||
|
||||
context 'when argument is the ids of designs' do
|
||||
|
|
|
|||
|
|
@ -135,9 +135,7 @@ describe('Pipeline DAG graph wrapper', () => {
|
|||
return waitForPromises();
|
||||
});
|
||||
|
||||
it('shows the graph and the beta alert', () => {
|
||||
expect(getAllAlerts().length).toBe(1);
|
||||
expect(getAlert().text()).toContain('This feature is currently in beta.');
|
||||
it('shows the graph', () => {
|
||||
expect(getGraph().exists()).toBe(true);
|
||||
});
|
||||
|
||||
|
|
@ -178,8 +176,7 @@ describe('Pipeline DAG graph wrapper', () => {
|
|||
});
|
||||
|
||||
it('does not render an error alert or the graph', () => {
|
||||
expect(getAllAlerts().length).toBe(1);
|
||||
expect(getAlert().text()).toContain('This feature is currently in beta.');
|
||||
expect(getAllAlerts().length).toBe(0);
|
||||
expect(getGraph().exists()).toBe(false);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -466,6 +466,7 @@ project:
|
|||
- vulnerability_identifiers
|
||||
- vulnerability_scanners
|
||||
- dast_site_profiles
|
||||
- dast_scanner_profiles
|
||||
- dast_sites
|
||||
- operations_feature_flags
|
||||
- operations_feature_flags_client
|
||||
|
|
|
|||
|
|
@ -768,6 +768,7 @@ DesignManagement::Design:
|
|||
- id
|
||||
- project_id
|
||||
- filename
|
||||
- relative_position
|
||||
DesignManagement::Action:
|
||||
- id
|
||||
- event
|
||||
|
|
|
|||
|
|
@ -38,6 +38,14 @@ RSpec.describe ApplicationRecord do
|
|||
expect { Suggestion.safe_find_or_create_by(build(:suggestion).attributes) }
|
||||
.to change { Suggestion.count }.by(1)
|
||||
end
|
||||
|
||||
it 'passes a block to find_or_create_by' do
|
||||
attributes = build(:suggestion).attributes
|
||||
|
||||
expect do |block|
|
||||
Suggestion.safe_find_or_create_by(attributes, &block)
|
||||
end.to yield_with_args(an_object_having_attributes(attributes))
|
||||
end
|
||||
end
|
||||
|
||||
describe '.safe_find_or_create_by!' do
|
||||
|
|
@ -51,6 +59,14 @@ RSpec.describe ApplicationRecord do
|
|||
it 'raises a validation error if the record was not persisted' do
|
||||
expect { Suggestion.find_or_create_by!(note: nil) }.to raise_error(ActiveRecord::RecordInvalid)
|
||||
end
|
||||
|
||||
it 'passes a block to find_or_create_by' do
|
||||
attributes = build(:suggestion).attributes
|
||||
|
||||
expect do |block|
|
||||
Suggestion.safe_find_or_create_by!(attributes, &block)
|
||||
end.to yield_with_args(an_object_having_attributes(attributes))
|
||||
end
|
||||
end
|
||||
|
||||
describe '.underscore' do
|
||||
|
|
|
|||
|
|
@ -34,6 +34,13 @@ RSpec.describe DesignManagement::DesignCollection do
|
|||
collection.find_or_create_design!(filename: 'world.jpg')
|
||||
end.not_to exceed_query_limit(1)
|
||||
end
|
||||
|
||||
it 'inserts the design after any existing designs' do
|
||||
design1 = collection.find_or_create_design!(filename: 'design1.jpg')
|
||||
design2 = collection.find_or_create_design!(filename: 'design2.jpg')
|
||||
|
||||
expect(design1.relative_position).to be < design2.relative_position
|
||||
end
|
||||
end
|
||||
|
||||
describe "#versions" do
|
||||
|
|
|
|||
|
|
@ -11,6 +11,11 @@ RSpec.describe DesignManagement::Design do
|
|||
let_it_be(:design3) { create(:design, :with_versions, issue: issue, versions_count: 1) }
|
||||
let_it_be(:deleted_design) { create(:design, :with_versions, deleted: true) }
|
||||
|
||||
it_behaves_like 'a class that supports relative positioning' do
|
||||
let(:factory) { :design }
|
||||
let(:default_params) { { issue: issue } }
|
||||
end
|
||||
|
||||
describe 'relations' do
|
||||
it { is_expected.to belong_to(:project) }
|
||||
it { is_expected.to belong_to(:issue) }
|
||||
|
|
@ -147,6 +152,39 @@ RSpec.describe DesignManagement::Design do
|
|||
end
|
||||
end
|
||||
|
||||
describe '.ordered' do
|
||||
before do
|
||||
design1.update!(relative_position: 2)
|
||||
design2.update!(relative_position: 1)
|
||||
design3.update!(relative_position: nil)
|
||||
deleted_design.update!(relative_position: nil)
|
||||
end
|
||||
|
||||
it 'sorts by relative position and ID in ascending order' do
|
||||
expect(described_class.ordered(issue.project)).to eq([design2, design1, design3, deleted_design])
|
||||
end
|
||||
|
||||
context 'when the :reorder_designs feature is enabled for the project' do
|
||||
before do
|
||||
stub_feature_flags(reorder_designs: issue.project)
|
||||
end
|
||||
|
||||
it 'sorts by relative position and ID in ascending order' do
|
||||
expect(described_class.ordered(issue.project)).to eq([design2, design1, design3, deleted_design])
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the :reorder_designs feature is disabled' do
|
||||
before do
|
||||
stub_feature_flags(reorder_designs: false)
|
||||
end
|
||||
|
||||
it 'sorts by ID in ascending order' do
|
||||
expect(described_class.ordered(issue.project)).to eq([design1, design2, design3, deleted_design])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.with_filename' do
|
||||
it 'returns correct design when passed a single filename' do
|
||||
expect(described_class.with_filename(design1.filename)).to eq([design1])
|
||||
|
|
|
|||
|
|
@ -16,42 +16,40 @@ RSpec.describe API::ComposerPackages do
|
|||
|
||||
subject { get api(url), headers: headers }
|
||||
|
||||
context 'without the need for a license' do
|
||||
context 'with valid project' do
|
||||
let!(:package) { create(:composer_package, :with_metadatum, project: project) }
|
||||
context 'with valid project' do
|
||||
let!(:package) { create(:composer_package, :with_metadatum, project: project) }
|
||||
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
|
||||
'PUBLIC' | :developer | true | true | 'Composer package index' | :success
|
||||
'PUBLIC' | :guest | true | true | 'Composer package index' | :success
|
||||
'PUBLIC' | :developer | true | false | 'Composer package index' | :success
|
||||
'PUBLIC' | :guest | true | false | 'Composer package index' | :success
|
||||
'PUBLIC' | :developer | false | true | 'Composer package index' | :success
|
||||
'PUBLIC' | :guest | false | true | 'Composer package index' | :success
|
||||
'PUBLIC' | :developer | false | false | 'Composer package index' | :success
|
||||
'PUBLIC' | :guest | false | false | 'Composer package index' | :success
|
||||
'PUBLIC' | :anonymous | false | true | 'Composer package index' | :success
|
||||
'PRIVATE' | :developer | true | true | 'Composer package index' | :success
|
||||
'PRIVATE' | :guest | true | true | 'Composer package index' | :success
|
||||
'PRIVATE' | :developer | true | false | 'process Composer api request' | :not_found
|
||||
'PRIVATE' | :guest | true | false | 'process Composer api request' | :not_found
|
||||
'PRIVATE' | :developer | false | true | 'process Composer api request' | :not_found
|
||||
'PRIVATE' | :guest | false | true | 'process Composer api request' | :not_found
|
||||
'PRIVATE' | :developer | false | false | 'process Composer api request' | :not_found
|
||||
'PRIVATE' | :guest | false | false | 'process Composer api request' | :not_found
|
||||
'PRIVATE' | :anonymous | false | true | 'process Composer api request' | :not_found
|
||||
end
|
||||
|
||||
with_them do
|
||||
include_context 'Composer api group access', params[:project_visibility_level], params[:user_role], params[:user_token] do
|
||||
it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
|
||||
end
|
||||
end
|
||||
where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
|
||||
'PUBLIC' | :developer | true | true | 'Composer package index' | :success
|
||||
'PUBLIC' | :guest | true | true | 'Composer package index' | :success
|
||||
'PUBLIC' | :developer | true | false | 'Composer package index' | :success
|
||||
'PUBLIC' | :guest | true | false | 'Composer package index' | :success
|
||||
'PUBLIC' | :developer | false | true | 'Composer package index' | :success
|
||||
'PUBLIC' | :guest | false | true | 'Composer package index' | :success
|
||||
'PUBLIC' | :developer | false | false | 'Composer package index' | :success
|
||||
'PUBLIC' | :guest | false | false | 'Composer package index' | :success
|
||||
'PUBLIC' | :anonymous | false | true | 'Composer package index' | :success
|
||||
'PRIVATE' | :developer | true | true | 'Composer package index' | :success
|
||||
'PRIVATE' | :guest | true | true | 'Composer package index' | :success
|
||||
'PRIVATE' | :developer | true | false | 'process Composer api request' | :not_found
|
||||
'PRIVATE' | :guest | true | false | 'process Composer api request' | :not_found
|
||||
'PRIVATE' | :developer | false | true | 'process Composer api request' | :not_found
|
||||
'PRIVATE' | :guest | false | true | 'process Composer api request' | :not_found
|
||||
'PRIVATE' | :developer | false | false | 'process Composer api request' | :not_found
|
||||
'PRIVATE' | :guest | false | false | 'process Composer api request' | :not_found
|
||||
'PRIVATE' | :anonymous | false | true | 'process Composer api request' | :not_found
|
||||
end
|
||||
|
||||
it_behaves_like 'rejects Composer access with unknown group id'
|
||||
with_them do
|
||||
include_context 'Composer api group access', params[:project_visibility_level], params[:user_role], params[:user_token] do
|
||||
it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'rejects Composer access with unknown group id'
|
||||
end
|
||||
|
||||
describe 'GET /api/v4/group/:id/-/packages/composer/p/:sha.json' do
|
||||
|
|
@ -61,40 +59,38 @@ RSpec.describe API::ComposerPackages do
|
|||
|
||||
subject { get api(url), headers: headers }
|
||||
|
||||
context 'without the need for a license' do
|
||||
context 'with valid project' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
context 'with valid project' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
|
||||
'PUBLIC' | :developer | true | true | 'Composer provider index' | :success
|
||||
'PUBLIC' | :guest | true | true | 'Composer provider index' | :success
|
||||
'PUBLIC' | :developer | true | false | 'Composer provider index' | :success
|
||||
'PUBLIC' | :guest | true | false | 'Composer provider index' | :success
|
||||
'PUBLIC' | :developer | false | true | 'Composer provider index' | :success
|
||||
'PUBLIC' | :guest | false | true | 'Composer provider index' | :success
|
||||
'PUBLIC' | :developer | false | false | 'Composer provider index' | :success
|
||||
'PUBLIC' | :guest | false | false | 'Composer provider index' | :success
|
||||
'PUBLIC' | :anonymous | false | true | 'Composer provider index' | :success
|
||||
'PRIVATE' | :developer | true | true | 'Composer provider index' | :success
|
||||
'PRIVATE' | :guest | true | true | 'Composer empty provider index' | :success
|
||||
'PRIVATE' | :developer | true | false | 'process Composer api request' | :not_found
|
||||
'PRIVATE' | :guest | true | false | 'process Composer api request' | :not_found
|
||||
'PRIVATE' | :developer | false | true | 'process Composer api request' | :not_found
|
||||
'PRIVATE' | :guest | false | true | 'process Composer api request' | :not_found
|
||||
'PRIVATE' | :developer | false | false | 'process Composer api request' | :not_found
|
||||
'PRIVATE' | :guest | false | false | 'process Composer api request' | :not_found
|
||||
'PRIVATE' | :anonymous | false | true | 'process Composer api request' | :not_found
|
||||
end
|
||||
|
||||
with_them do
|
||||
include_context 'Composer api group access', params[:project_visibility_level], params[:user_role], params[:user_token] do
|
||||
it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
|
||||
end
|
||||
end
|
||||
where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
|
||||
'PUBLIC' | :developer | true | true | 'Composer provider index' | :success
|
||||
'PUBLIC' | :guest | true | true | 'Composer provider index' | :success
|
||||
'PUBLIC' | :developer | true | false | 'Composer provider index' | :success
|
||||
'PUBLIC' | :guest | true | false | 'Composer provider index' | :success
|
||||
'PUBLIC' | :developer | false | true | 'Composer provider index' | :success
|
||||
'PUBLIC' | :guest | false | true | 'Composer provider index' | :success
|
||||
'PUBLIC' | :developer | false | false | 'Composer provider index' | :success
|
||||
'PUBLIC' | :guest | false | false | 'Composer provider index' | :success
|
||||
'PUBLIC' | :anonymous | false | true | 'Composer provider index' | :success
|
||||
'PRIVATE' | :developer | true | true | 'Composer provider index' | :success
|
||||
'PRIVATE' | :guest | true | true | 'Composer empty provider index' | :success
|
||||
'PRIVATE' | :developer | true | false | 'process Composer api request' | :not_found
|
||||
'PRIVATE' | :guest | true | false | 'process Composer api request' | :not_found
|
||||
'PRIVATE' | :developer | false | true | 'process Composer api request' | :not_found
|
||||
'PRIVATE' | :guest | false | true | 'process Composer api request' | :not_found
|
||||
'PRIVATE' | :developer | false | false | 'process Composer api request' | :not_found
|
||||
'PRIVATE' | :guest | false | false | 'process Composer api request' | :not_found
|
||||
'PRIVATE' | :anonymous | false | true | 'process Composer api request' | :not_found
|
||||
end
|
||||
|
||||
it_behaves_like 'rejects Composer access with unknown group id'
|
||||
with_them do
|
||||
include_context 'Composer api group access', params[:project_visibility_level], params[:user_role], params[:user_token] do
|
||||
it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'rejects Composer access with unknown group id'
|
||||
end
|
||||
|
||||
describe 'GET /api/v4/group/:id/-/packages/composer/*package_name.json' do
|
||||
|
|
@ -103,48 +99,46 @@ RSpec.describe API::ComposerPackages do
|
|||
|
||||
subject { get api(url), headers: headers }
|
||||
|
||||
context 'without the need for a license' do
|
||||
context 'with no packages' do
|
||||
include_context 'Composer user type', :developer, true do
|
||||
it_behaves_like 'returning response status', :not_found
|
||||
end
|
||||
context 'with no packages' do
|
||||
include_context 'Composer user type', :developer, true do
|
||||
it_behaves_like 'returning response status', :not_found
|
||||
end
|
||||
|
||||
context 'with valid project' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
let!(:package) { create(:composer_package, :with_metadatum, name: package_name, project: project) }
|
||||
|
||||
where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
|
||||
'PUBLIC' | :developer | true | true | 'Composer package api request' | :success
|
||||
'PUBLIC' | :guest | true | true | 'Composer package api request' | :success
|
||||
'PUBLIC' | :developer | true | false | 'Composer package api request' | :success
|
||||
'PUBLIC' | :guest | true | false | 'Composer package api request' | :success
|
||||
'PUBLIC' | :developer | false | true | 'Composer package api request' | :success
|
||||
'PUBLIC' | :guest | false | true | 'Composer package api request' | :success
|
||||
'PUBLIC' | :developer | false | false | 'Composer package api request' | :success
|
||||
'PUBLIC' | :guest | false | false | 'Composer package api request' | :success
|
||||
'PUBLIC' | :anonymous | false | true | 'Composer package api request' | :success
|
||||
'PRIVATE' | :developer | true | true | 'Composer package api request' | :success
|
||||
'PRIVATE' | :guest | true | true | 'process Composer api request' | :not_found
|
||||
'PRIVATE' | :developer | true | false | 'process Composer api request' | :not_found
|
||||
'PRIVATE' | :guest | true | false | 'process Composer api request' | :not_found
|
||||
'PRIVATE' | :developer | false | true | 'process Composer api request' | :not_found
|
||||
'PRIVATE' | :guest | false | true | 'process Composer api request' | :not_found
|
||||
'PRIVATE' | :developer | false | false | 'process Composer api request' | :not_found
|
||||
'PRIVATE' | :guest | false | false | 'process Composer api request' | :not_found
|
||||
'PRIVATE' | :anonymous | false | true | 'process Composer api request' | :not_found
|
||||
end
|
||||
|
||||
with_them do
|
||||
include_context 'Composer api group access', params[:project_visibility_level], params[:user_role], params[:user_token] do
|
||||
it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'rejects Composer access with unknown group id'
|
||||
end
|
||||
|
||||
context 'with valid project' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
let!(:package) { create(:composer_package, :with_metadatum, name: package_name, project: project) }
|
||||
|
||||
where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
|
||||
'PUBLIC' | :developer | true | true | 'Composer package api request' | :success
|
||||
'PUBLIC' | :guest | true | true | 'Composer package api request' | :success
|
||||
'PUBLIC' | :developer | true | false | 'Composer package api request' | :success
|
||||
'PUBLIC' | :guest | true | false | 'Composer package api request' | :success
|
||||
'PUBLIC' | :developer | false | true | 'Composer package api request' | :success
|
||||
'PUBLIC' | :guest | false | true | 'Composer package api request' | :success
|
||||
'PUBLIC' | :developer | false | false | 'Composer package api request' | :success
|
||||
'PUBLIC' | :guest | false | false | 'Composer package api request' | :success
|
||||
'PUBLIC' | :anonymous | false | true | 'Composer package api request' | :success
|
||||
'PRIVATE' | :developer | true | true | 'Composer package api request' | :success
|
||||
'PRIVATE' | :guest | true | true | 'process Composer api request' | :not_found
|
||||
'PRIVATE' | :developer | true | false | 'process Composer api request' | :not_found
|
||||
'PRIVATE' | :guest | true | false | 'process Composer api request' | :not_found
|
||||
'PRIVATE' | :developer | false | true | 'process Composer api request' | :not_found
|
||||
'PRIVATE' | :guest | false | true | 'process Composer api request' | :not_found
|
||||
'PRIVATE' | :developer | false | false | 'process Composer api request' | :not_found
|
||||
'PRIVATE' | :guest | false | false | 'process Composer api request' | :not_found
|
||||
'PRIVATE' | :anonymous | false | true | 'process Composer api request' | :not_found
|
||||
end
|
||||
|
||||
with_them do
|
||||
include_context 'Composer api group access', params[:project_visibility_level], params[:user_role], params[:user_token] do
|
||||
it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'rejects Composer access with unknown group id'
|
||||
end
|
||||
|
||||
describe 'POST /api/v4/projects/:id/packages/composer' do
|
||||
|
|
@ -158,40 +152,38 @@ RSpec.describe API::ComposerPackages do
|
|||
subject { post api(url), headers: headers, params: params }
|
||||
|
||||
shared_examples 'composer package publish' do
|
||||
context 'without the need for a license' do
|
||||
context 'with valid project' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
context 'with valid project' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
|
||||
'PUBLIC' | :developer | true | true | 'Composer package creation' | :created
|
||||
'PUBLIC' | :guest | true | true | 'process Composer api request' | :forbidden
|
||||
'PUBLIC' | :developer | true | false | 'process Composer api request' | :unauthorized
|
||||
'PUBLIC' | :guest | true | false | 'process Composer api request' | :unauthorized
|
||||
'PUBLIC' | :developer | false | true | 'process Composer api request' | :forbidden
|
||||
'PUBLIC' | :guest | false | true | 'process Composer api request' | :forbidden
|
||||
'PUBLIC' | :developer | false | false | 'process Composer api request' | :unauthorized
|
||||
'PUBLIC' | :guest | false | false | 'process Composer api request' | :unauthorized
|
||||
'PUBLIC' | :anonymous | false | true | 'process Composer api request' | :unauthorized
|
||||
'PRIVATE' | :developer | true | true | 'Composer package creation' | :created
|
||||
'PRIVATE' | :guest | true | true | 'process Composer api request' | :forbidden
|
||||
'PRIVATE' | :developer | true | false | 'process Composer api request' | :unauthorized
|
||||
'PRIVATE' | :guest | true | false | 'process Composer api request' | :unauthorized
|
||||
'PRIVATE' | :developer | false | true | 'process Composer api request' | :not_found
|
||||
'PRIVATE' | :guest | false | true | 'process Composer api request' | :not_found
|
||||
'PRIVATE' | :developer | false | false | 'process Composer api request' | :unauthorized
|
||||
'PRIVATE' | :guest | false | false | 'process Composer api request' | :unauthorized
|
||||
'PRIVATE' | :anonymous | false | true | 'process Composer api request' | :unauthorized
|
||||
end
|
||||
|
||||
with_them do
|
||||
include_context 'Composer api project access', params[:project_visibility_level], params[:user_role], params[:user_token] do
|
||||
it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
|
||||
end
|
||||
end
|
||||
where(:project_visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
|
||||
'PUBLIC' | :developer | true | true | 'Composer package creation' | :created
|
||||
'PUBLIC' | :guest | true | true | 'process Composer api request' | :forbidden
|
||||
'PUBLIC' | :developer | true | false | 'process Composer api request' | :unauthorized
|
||||
'PUBLIC' | :guest | true | false | 'process Composer api request' | :unauthorized
|
||||
'PUBLIC' | :developer | false | true | 'process Composer api request' | :forbidden
|
||||
'PUBLIC' | :guest | false | true | 'process Composer api request' | :forbidden
|
||||
'PUBLIC' | :developer | false | false | 'process Composer api request' | :unauthorized
|
||||
'PUBLIC' | :guest | false | false | 'process Composer api request' | :unauthorized
|
||||
'PUBLIC' | :anonymous | false | true | 'process Composer api request' | :unauthorized
|
||||
'PRIVATE' | :developer | true | true | 'Composer package creation' | :created
|
||||
'PRIVATE' | :guest | true | true | 'process Composer api request' | :forbidden
|
||||
'PRIVATE' | :developer | true | false | 'process Composer api request' | :unauthorized
|
||||
'PRIVATE' | :guest | true | false | 'process Composer api request' | :unauthorized
|
||||
'PRIVATE' | :developer | false | true | 'process Composer api request' | :not_found
|
||||
'PRIVATE' | :guest | false | true | 'process Composer api request' | :not_found
|
||||
'PRIVATE' | :developer | false | false | 'process Composer api request' | :unauthorized
|
||||
'PRIVATE' | :guest | false | false | 'process Composer api request' | :unauthorized
|
||||
'PRIVATE' | :anonymous | false | true | 'process Composer api request' | :unauthorized
|
||||
end
|
||||
|
||||
it_behaves_like 'rejects Composer access with unknown project id'
|
||||
with_them do
|
||||
include_context 'Composer api project access', params[:project_visibility_level], params[:user_role], params[:user_token] do
|
||||
it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'rejects Composer access with unknown project id'
|
||||
end
|
||||
|
||||
context 'with no tag or branch params' do
|
||||
|
|
@ -238,65 +230,63 @@ RSpec.describe API::ComposerPackages do
|
|||
|
||||
subject { get api(url), headers: headers, params: params }
|
||||
|
||||
context 'without the need for a license' do
|
||||
context 'with valid project' do
|
||||
let!(:package) { create(:composer_package, :with_metadatum, name: package_name, project: project) }
|
||||
context 'with valid project' do
|
||||
let!(:package) { create(:composer_package, :with_metadatum, name: package_name, project: project) }
|
||||
|
||||
context 'when the sha does not match the package name' do
|
||||
let(:sha) { '123' }
|
||||
context 'when the sha does not match the package name' do
|
||||
let(:sha) { '123' }
|
||||
|
||||
it_behaves_like 'process Composer api request', :anonymous, :not_found
|
||||
end
|
||||
|
||||
context 'when the package name does not match the sha' do
|
||||
let(:branch) { project.repository.find_branch('master') }
|
||||
let(:sha) { branch.target }
|
||||
let(:url) { "/projects/#{project.id}/packages/composer/archives/unexisting-package-name.zip" }
|
||||
|
||||
it_behaves_like 'process Composer api request', :anonymous, :not_found
|
||||
end
|
||||
|
||||
context 'with a match package name and sha' do
|
||||
let(:branch) { project.repository.find_branch('master') }
|
||||
let(:sha) { branch.target }
|
||||
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
where(:project_visibility_level, :user_role, :member, :user_token, :expected_status) do
|
||||
'PUBLIC' | :developer | true | true | :success
|
||||
'PUBLIC' | :guest | true | true | :success
|
||||
'PUBLIC' | :developer | true | false | :success
|
||||
'PUBLIC' | :guest | true | false | :success
|
||||
'PUBLIC' | :developer | false | true | :success
|
||||
'PUBLIC' | :guest | false | true | :success
|
||||
'PUBLIC' | :developer | false | false | :success
|
||||
'PUBLIC' | :guest | false | false | :success
|
||||
'PUBLIC' | :anonymous | false | true | :success
|
||||
'PRIVATE' | :developer | true | true | :success
|
||||
'PRIVATE' | :guest | true | true | :success
|
||||
'PRIVATE' | :developer | true | false | :success
|
||||
'PRIVATE' | :guest | true | false | :success
|
||||
'PRIVATE' | :developer | false | true | :success
|
||||
'PRIVATE' | :guest | false | true | :success
|
||||
'PRIVATE' | :developer | false | false | :success
|
||||
'PRIVATE' | :guest | false | false | :success
|
||||
'PRIVATE' | :anonymous | false | true | :success
|
||||
end
|
||||
|
||||
with_them do
|
||||
let(:token) { user_token ? personal_access_token.token : 'wrong' }
|
||||
let(:headers) { user_role == :anonymous ? {} : build_basic_auth_header(user.username, token) }
|
||||
|
||||
before do
|
||||
project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
|
||||
end
|
||||
|
||||
it_behaves_like 'process Composer api request', params[:user_role], params[:expected_status], params[:member]
|
||||
end
|
||||
end
|
||||
it_behaves_like 'process Composer api request', :anonymous, :not_found
|
||||
end
|
||||
|
||||
it_behaves_like 'rejects Composer access with unknown project id'
|
||||
context 'when the package name does not match the sha' do
|
||||
let(:branch) { project.repository.find_branch('master') }
|
||||
let(:sha) { branch.target }
|
||||
let(:url) { "/projects/#{project.id}/packages/composer/archives/unexisting-package-name.zip" }
|
||||
|
||||
it_behaves_like 'process Composer api request', :anonymous, :not_found
|
||||
end
|
||||
|
||||
context 'with a match package name and sha' do
|
||||
let(:branch) { project.repository.find_branch('master') }
|
||||
let(:sha) { branch.target }
|
||||
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
where(:project_visibility_level, :user_role, :member, :user_token, :expected_status) do
|
||||
'PUBLIC' | :developer | true | true | :success
|
||||
'PUBLIC' | :guest | true | true | :success
|
||||
'PUBLIC' | :developer | true | false | :success
|
||||
'PUBLIC' | :guest | true | false | :success
|
||||
'PUBLIC' | :developer | false | true | :success
|
||||
'PUBLIC' | :guest | false | true | :success
|
||||
'PUBLIC' | :developer | false | false | :success
|
||||
'PUBLIC' | :guest | false | false | :success
|
||||
'PUBLIC' | :anonymous | false | true | :success
|
||||
'PRIVATE' | :developer | true | true | :success
|
||||
'PRIVATE' | :guest | true | true | :success
|
||||
'PRIVATE' | :developer | true | false | :success
|
||||
'PRIVATE' | :guest | true | false | :success
|
||||
'PRIVATE' | :developer | false | true | :success
|
||||
'PRIVATE' | :guest | false | true | :success
|
||||
'PRIVATE' | :developer | false | false | :success
|
||||
'PRIVATE' | :guest | false | false | :success
|
||||
'PRIVATE' | :anonymous | false | true | :success
|
||||
end
|
||||
|
||||
with_them do
|
||||
let(:token) { user_token ? personal_access_token.token : 'wrong' }
|
||||
let(:headers) { user_role == :anonymous ? {} : build_basic_auth_header(user.username, token) }
|
||||
|
||||
before do
|
||||
project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
|
||||
end
|
||||
|
||||
it_behaves_like 'process Composer api request', params[:user_role], params[:expected_status], params[:member]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'rejects Composer access with unknown project id'
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -123,16 +123,19 @@ RSpec.describe API::ProjectSnippets do
|
|||
end
|
||||
|
||||
describe 'POST /projects/:project_id/snippets/' do
|
||||
let(:params) do
|
||||
let(:base_params) do
|
||||
{
|
||||
title: 'Test Title',
|
||||
file_name: 'test.rb',
|
||||
description: 'test description',
|
||||
content: 'puts "hello world"',
|
||||
visibility: 'public'
|
||||
}
|
||||
end
|
||||
|
||||
let(:file_path) { 'file_1.rb' }
|
||||
let(:file_content) { 'puts "hello world"' }
|
||||
let(:params) { base_params.merge(file_params) }
|
||||
let(:file_params) { { files: [{ file_path: file_path, content: file_content }] } }
|
||||
|
||||
shared_examples 'project snippet repository actions' do
|
||||
let(:snippet) { ProjectSnippet.find(json_response['id']) }
|
||||
|
||||
|
|
@ -145,9 +148,9 @@ RSpec.describe API::ProjectSnippets do
|
|||
it 'commit the files to the repository' do
|
||||
subject
|
||||
|
||||
blob = snippet.repository.blob_at('master', params[:file_name])
|
||||
blob = snippet.repository.blob_at('master', file_path)
|
||||
|
||||
expect(blob.data).to eq params[:content]
|
||||
expect(blob.data).to eq file_content
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -184,63 +187,60 @@ RSpec.describe API::ProjectSnippets do
|
|||
params['visibility'] = 'internal'
|
||||
end
|
||||
|
||||
subject { post api("/projects/#{project.id}/snippets/", user), params: params }
|
||||
|
||||
it 'creates a new snippet' do
|
||||
post api("/projects/#{project.id}/snippets/", user), params: params
|
||||
subject
|
||||
|
||||
expect(response).to have_gitlab_http_status(:created)
|
||||
snippet = ProjectSnippet.find(json_response['id'])
|
||||
expect(snippet.content).to eq(params[:content])
|
||||
expect(snippet.content).to eq(file_content)
|
||||
expect(snippet.description).to eq(params[:description])
|
||||
expect(snippet.title).to eq(params[:title])
|
||||
expect(snippet.file_name).to eq(params[:file_name])
|
||||
expect(snippet.file_name).to eq(file_path)
|
||||
expect(snippet.visibility_level).to eq(Snippet::INTERNAL)
|
||||
end
|
||||
|
||||
it_behaves_like 'project snippet repository actions' do
|
||||
subject { post api("/projects/#{project.id}/snippets/", user), params: params }
|
||||
end
|
||||
it_behaves_like 'project snippet repository actions'
|
||||
end
|
||||
|
||||
it 'creates a new snippet' do
|
||||
post api("/projects/#{project.id}/snippets/", admin), params: params
|
||||
|
||||
expect(response).to have_gitlab_http_status(:created)
|
||||
snippet = ProjectSnippet.find(json_response['id'])
|
||||
expect(snippet.content).to eq(params[:content])
|
||||
expect(snippet.description).to eq(params[:description])
|
||||
expect(snippet.title).to eq(params[:title])
|
||||
expect(snippet.file_name).to eq(params[:file_name])
|
||||
expect(snippet.visibility_level).to eq(Snippet::PUBLIC)
|
||||
end
|
||||
|
||||
it_behaves_like 'project snippet repository actions' do
|
||||
context 'with an admin' do
|
||||
subject { post api("/projects/#{project.id}/snippets/", admin), params: params }
|
||||
end
|
||||
|
||||
it 'returns 400 for missing parameters' do
|
||||
params.delete(:title)
|
||||
it 'creates a new snippet' do
|
||||
subject
|
||||
|
||||
post api("/projects/#{project.id}/snippets/", admin), params: params
|
||||
expect(response).to have_gitlab_http_status(:created)
|
||||
snippet = ProjectSnippet.find(json_response['id'])
|
||||
expect(snippet.content).to eq(file_content)
|
||||
expect(snippet.description).to eq(params[:description])
|
||||
expect(snippet.title).to eq(params[:title])
|
||||
expect(snippet.file_name).to eq(file_path)
|
||||
expect(snippet.visibility_level).to eq(Snippet::PUBLIC)
|
||||
end
|
||||
|
||||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
end
|
||||
it_behaves_like 'project snippet repository actions'
|
||||
|
||||
it 'returns 400 if content is blank' do
|
||||
params[:content] = ''
|
||||
it 'returns 400 for missing parameters' do
|
||||
params.delete(:title)
|
||||
|
||||
post api("/projects/#{project.id}/snippets/", admin), params: params
|
||||
subject
|
||||
|
||||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
expect(json_response['error']).to eq 'content is empty'
|
||||
end
|
||||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
end
|
||||
|
||||
it 'returns 400 if title is blank' do
|
||||
params[:title] = ''
|
||||
it_behaves_like 'snippet creation with files parameter'
|
||||
|
||||
post api("/projects/#{project.id}/snippets/", admin), params: params
|
||||
it_behaves_like 'snippet creation without files parameter'
|
||||
|
||||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
expect(json_response['error']).to eq 'title is empty'
|
||||
it 'returns 400 if title is blank' do
|
||||
params[:title] = ''
|
||||
|
||||
subject
|
||||
|
||||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
expect(json_response['error']).to eq 'title is empty'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when save fails because the repository could not be created' do
|
||||
|
|
|
|||
|
|
@ -274,52 +274,7 @@ RSpec.describe API::Snippets do
|
|||
end
|
||||
|
||||
context 'with files parameter' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
where(:path, :content, :status, :error) do
|
||||
'.gitattributes' | 'file content' | :created | nil
|
||||
'valid/path/file.rb' | 'file content' | :created | nil
|
||||
|
||||
'.gitattributes' | nil | :bad_request | 'files[0][content] is empty'
|
||||
'.gitattributes' | '' | :bad_request | 'files[0][content] is empty'
|
||||
|
||||
'' | 'file content' | :bad_request | 'files[0][file_path] is empty'
|
||||
nil | 'file content' | :bad_request | 'files[0][file_path] should be a valid file path, files[0][file_path] is empty'
|
||||
'../../etc/passwd' | 'file content' | :bad_request | 'files[0][file_path] should be a valid file path'
|
||||
end
|
||||
|
||||
with_them do
|
||||
let(:file_path) { path }
|
||||
let(:file_content) { content }
|
||||
|
||||
before do
|
||||
subject
|
||||
end
|
||||
|
||||
it 'responds correctly' do
|
||||
expect(response).to have_gitlab_http_status(status)
|
||||
expect(json_response['error']).to eq(error)
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns 400 if both files and content are provided' do
|
||||
params[:file_name] = 'foo.rb'
|
||||
params[:content] = 'bar'
|
||||
|
||||
subject
|
||||
|
||||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
expect(json_response['error']).to eq 'files, content are mutually exclusive'
|
||||
end
|
||||
|
||||
it 'returns 400 when neither files or content are provided' do
|
||||
params.delete(:files)
|
||||
|
||||
subject
|
||||
|
||||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
expect(json_response['error']).to eq 'files, content are missing, exactly one parameter must be provided'
|
||||
end
|
||||
it_behaves_like 'snippet creation with files parameter'
|
||||
|
||||
context 'with multiple files' do
|
||||
let(:file_params) do
|
||||
|
|
@ -335,24 +290,7 @@ RSpec.describe API::Snippets do
|
|||
end
|
||||
end
|
||||
|
||||
context 'without files parameter' do
|
||||
let(:file_params) { { file_name: 'testing.rb', content: 'snippet content' } }
|
||||
|
||||
it 'allows file_name and content parameters' do
|
||||
subject
|
||||
|
||||
expect(response).to have_gitlab_http_status(:created)
|
||||
end
|
||||
|
||||
it 'returns 400 if file_name and content are not both provided' do
|
||||
params.delete(:file_name)
|
||||
|
||||
subject
|
||||
|
||||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
expect(json_response['error']).to eq 'file_name is missing'
|
||||
end
|
||||
end
|
||||
it_behaves_like 'snippet creation without files parameter'
|
||||
|
||||
context 'with restricted visibility settings' do
|
||||
before do
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue