Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-08-05 15:09:59 +00:00
parent 25bfb256b3
commit 71da5de44f
103 changed files with 1324 additions and 586 deletions

View File

@ -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.

View File

@ -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" />

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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.
*/

View File

@ -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);
}
}

View File

@ -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;

View File

@ -183,7 +183,7 @@
.option-description,
.option-disabled-reason {
margin-left: 30px;
margin-left: 20px;
color: $project-option-descr-color;
margin-top: -5px;
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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')

View File

@ -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')

View File

@ -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')

View File

@ -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

View File

@ -0,0 +1,5 @@
---
title: Order projects within the project dropdown by relevance in analytics features
merge_request: 38675
author:
type: changed

View File

@ -0,0 +1,5 @@
---
title: Add relative positioning on designs
merge_request: 37835
author:
type: changed

View File

@ -0,0 +1,5 @@
---
title: Take DAG view out of beta
merge_request: 38517
author:
type: changed

View File

@ -0,0 +1,5 @@
---
title: Fix vertical alignment of svg icons on Jobs page
merge_request: 38656
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Button migration vulnerability charts
merge_request: 38610
author:
type: changed

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -0,0 +1 @@
d4e389b1469b968b703432de9ece6c362e45ec4ba3ad20d1f6c6418253969379

View File

@ -0,0 +1 @@
a16e7fdcc62f39af3038317cb39ffb4c35f41ae45f5de429f18837309739110b

View File

@ -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);

View File

@ -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
"""

View File

@ -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",

View File

@ -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

View File

@ -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:

View File

@ -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:

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
![Status Page landing page](img/status_page_incidents_v12_10.png)
Clicking an incident displays a detail page with more information about a particular incident:
![Status Page detail](img/status_page_detail_v12_10.png)
- 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.
![Status Page landing page](img/status_page_incidents_v12_10.png)
### 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.
![Status Page detail](img/status_page_detail_v12_10.png)
## 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.
![Status Page detail link](img/status_page_detail_link_v13_1.png)
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.

View File

@ -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),

View File

@ -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).

View File

@ -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

View File

@ -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 }

View File

@ -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

View File

@ -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]

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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|

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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')

View File

@ -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?

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -39,7 +39,7 @@ module Gitlab
end
def first_attempt?
attempts.zero?
attempts == 0
end
def sleep_sec

View File

@ -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?

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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}"

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 ... '

View File

@ -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(

View File

@ -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"

View File

@ -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])

View File

@ -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 ""

View File

@ -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

View File

@ -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);
});

View File

@ -466,6 +466,7 @@ project:
- vulnerability_identifiers
- vulnerability_scanners
- dast_site_profiles
- dast_scanner_profiles
- dast_sites
- operations_feature_flags
- operations_feature_flags_client

View File

@ -768,6 +768,7 @@ DesignManagement::Design:
- id
- project_id
- filename
- relative_position
DesignManagement::Action:
- id
- event

View File

@ -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

View File

@ -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

View File

@ -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])

View File

@ -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

View File

@ -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

View File

@ -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