summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.rubocop_todo.yml52
-rw-r--r--app/assets/javascripts/boards/components/board_list_header.vue2
-rw-r--r--app/assets/javascripts/incidents/components/incidents_list.vue2
-rw-r--r--app/assets/javascripts/incidents/graphql/queries/get_incidents.query.graphql4
-rw-r--r--app/assets/javascripts/pipelines/components/dag/dag.vue21
-rw-r--r--app/assets/stylesheets/framework/variables.scss5
-rw-r--r--app/assets/stylesheets/pages/boards.scss29
-rw-r--r--app/assets/stylesheets/pages/builds.scss4
-rw-r--r--app/assets/stylesheets/pages/settings.scss2
-rw-r--r--app/controllers/projects/metrics/dashboards/builder_controller.rb42
-rw-r--r--app/finders/design_management/designs_finder.rb4
-rw-r--r--app/models/application_record.rb8
-rw-r--r--app/models/design_management/design.rb23
-rw-r--r--app/models/design_management/design_collection.rb4
-rw-r--r--app/services/metrics/dashboard/panel_preview_service.rb54
-rw-r--r--app/views/admin/application_settings/ci/_header.html.haml6
-rw-r--r--app/views/ci/variables/_content.html.haml9
-rw-r--r--app/views/projects/pipelines/_with_tabs.html.haml1
-rw-r--r--app/views/shared/issuable/_search_bar.html.haml2
-rw-r--r--changelogs/unreleased/221043-enable-similarity-search-feature-flags-by-default.yml5
-rw-r--r--changelogs/unreleased/221167-model-changes.yml5
-rw-r--r--changelogs/unreleased/223261-move-pipeline-header-to-graphql.yml5
-rw-r--r--changelogs/unreleased/233426-justin_ho-fix-svg-alignment-jobs-page.yml5
-rw-r--r--changelogs/unreleased/migrate-btn-vul-charts.yml5
-rw-r--r--config/feature_flags/development/reorder_designs.yml7
-rw-r--r--db/migrate/20200722091435_add_relative_position_to_design_management_designs.rb9
-rw-r--r--db/migrate/20200728174137_add_index_on_design_management_designs_issue_id_and_relative_position_and_id.rb18
-rw-r--r--db/schema_migrations/202007220914351
-rw-r--r--db/schema_migrations/202007281741371
-rw-r--r--db/structure.sql5
-rw-r--r--doc/api/graphql/reference/gitlab_schema.graphql85
-rw-r--r--doc/api/graphql/reference/gitlab_schema.json238
-rw-r--r--doc/api/graphql/reference/index.md11
-rw-r--r--doc/ci/directed_acyclic_graph/index.md5
-rw-r--r--doc/ci/yaml/README.md6
-rw-r--r--doc/development/auto_devops.md6
-rw-r--r--doc/development/chatops_on_gitlabcom.md6
-rw-r--r--doc/development/go_guide/dependencies.md6
-rw-r--r--doc/development/go_guide/index.md6
-rw-r--r--doc/development/kubernetes.md6
-rw-r--r--doc/operations/incident_management/status_page.md203
-rw-r--r--doc/user/group/epics/index.md3
-rw-r--r--doc/user/project/settings/index.md4
-rw-r--r--lib/api/entities/feature.rb2
-rw-r--r--lib/api/groups.rb2
-rw-r--r--lib/api/helpers/pagination_strategies.rb2
-rw-r--r--lib/api/project_snippets.rb10
-rw-r--r--lib/backup/files.rb2
-rw-r--r--lib/banzai/filter/gollum_tags_filter.rb2
-rw-r--r--lib/bitbucket_server/paginator.rb2
-rw-r--r--lib/declarative_policy/runner.rb2
-rw-r--r--lib/gitlab/auth/ldap/adapter.rb2
-rw-r--r--lib/gitlab/bare_repository_import/importer.rb2
-rw-r--r--lib/gitlab/ci/config/external/context.rb2
-rw-r--r--lib/gitlab/ci/reports/accessibility_reports_comparer.rb2
-rw-r--r--lib/gitlab/cycle_analytics/summary/value.rb2
-rw-r--r--lib/gitlab/cycle_analytics/summary_helper.rb2
-rw-r--r--lib/gitlab/danger/teammate.rb2
-rw-r--r--lib/gitlab/database.rb2
-rw-r--r--lib/gitlab/database/connection_timer.rb2
-rw-r--r--lib/gitlab/database/migration_helpers.rb2
-rw-r--r--lib/gitlab/exclusive_lease.rb2
-rw-r--r--lib/gitlab/exclusive_lease_helpers/sleeping_lock.rb2
-rw-r--r--lib/gitlab/experimentation.rb2
-rw-r--r--lib/gitlab/file_hook.rb2
-rw-r--r--lib/gitlab/git/commit.rb4
-rw-r--r--lib/gitlab/git/repository.rb2
-rw-r--r--lib/gitlab/git/rugged_impl/blob.rb2
-rw-r--r--lib/gitlab/gitaly_client.rb2
-rw-r--r--lib/gitlab/github_import/user_finder.rb2
-rw-r--r--lib/gitlab/hashed_storage/migrator.rb2
-rw-r--r--lib/gitlab/import_export/command_line_util.rb4
-rw-r--r--lib/gitlab/multi_collection_paginator.rb2
-rw-r--r--lib/gitlab/polling_interval.rb2
-rw-r--r--lib/gitlab/project_search_results.rb2
-rw-r--r--lib/gitlab/seeder.rb2
-rw-r--r--lib/gitlab/sidekiq_cluster.rb2
-rw-r--r--lib/gitlab/sidekiq_daemon/memory_killer.rb2
-rw-r--r--lib/gitlab/sidekiq_middleware/memory_killer.rb2
-rw-r--r--lib/gitlab/sidekiq_status.rb2
-rw-r--r--lib/gitlab/slash_commands/presenters/issue_show.rb8
-rw-r--r--lib/gitlab/task_helpers.rb2
-rw-r--r--lib/gitlab/untrusted_regexp.rb4
-rw-r--r--lib/gitlab/usage_data/topology.rb2
-rw-r--r--lib/gitlab/utils.rb2
-rw-r--r--lib/system_check/sidekiq_check.rb2
-rw-r--r--lib/tasks/gitlab/gitaly.rake2
-rw-r--r--lib/tasks/gitlab/snippets.rake8
-rw-r--r--lib/tasks/gitlab/workhorse.rake2
-rw-r--r--locale/gitlab.pot18
-rw-r--r--spec/finders/design_management/designs_finder_spec.rb30
-rw-r--r--spec/frontend/pipelines/components/dag/dag_spec.js7
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml1
-rw-r--r--spec/lib/gitlab/import_export/safe_model_attributes.yml1
-rw-r--r--spec/models/application_record_spec.rb16
-rw-r--r--spec/models/design_management/design_collection_spec.rb7
-rw-r--r--spec/models/design_management/design_spec.rb38
-rw-r--r--spec/requests/api/composer_packages_spec.rb342
-rw-r--r--spec/requests/api/project_snippets_spec.rb80
-rw-r--r--spec/requests/api/snippets_spec.rb66
-rw-r--r--spec/requests/projects/metrics/dashboards/builder_spec.rb67
-rw-r--r--spec/services/metrics/dashboard/panel_preview_service_spec.rb83
-rw-r--r--spec/support/shared_examples/requests/snippet_shared_examples.rb77
103 files changed, 1307 insertions, 569 deletions
diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
index 2c2d3bfe90b..64b3faa9e07 100644
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
@@ -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.
diff --git a/app/assets/javascripts/boards/components/board_list_header.vue b/app/assets/javascripts/boards/components/board_list_header.vue
index 02a04cb4e46..d03a4fb1e07 100644
--- a/app/assets/javascripts/boards/components/board_list_header.vue
+++ b/app/assets/javascripts/boards/components/board_list_header.vue
@@ -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" />
diff --git a/app/assets/javascripts/incidents/components/incidents_list.vue b/app/assets/javascripts/incidents/components/incidents_list.vue
index 7011b808f9e..3291f2aa8b6 100644
--- a/app/assets/javascripts/incidents/components/incidents_list.vue
+++ b/app/assets/javascripts/incidents/components/incidents_list.vue
@@ -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,
diff --git a/app/assets/javascripts/incidents/graphql/queries/get_incidents.query.graphql b/app/assets/javascripts/incidents/graphql/queries/get_incidents.query.graphql
index c0942b629e5..789086ab43c 100644
--- a/app/assets/javascripts/incidents/graphql/queries/get_incidents.query.graphql
+++ b/app/assets/javascripts/incidents/graphql/queries/get_incidents.query.graphql
@@ -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
diff --git a/app/assets/javascripts/pipelines/components/dag/dag.vue b/app/assets/javascripts/pipelines/components/dag/dag.vue
index 85163a666e2..10a9703a4c7 100644
--- a/app/assets/javascripts/pipelines/components/dag/dag.vue
+++ b/app/assets/javascripts/pipelines/components/dag/dag.vue
@@ -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
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index ed4f3805cc4..f5524404461 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -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.
*/
diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss
index a358fe2b48a..c404b5da5c9 100644
--- a/app/assets/stylesheets/pages/boards.scss
+++ b/app/assets/stylesheets/pages/boards.scss
@@ -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);
+ }
+}
diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss
index 02c42d5b779..f367d9ea4d8 100644
--- a/app/assets/stylesheets/pages/builds.scss
+++ b/app/assets/stylesheets/pages/builds.scss
@@ -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;
diff --git a/app/assets/stylesheets/pages/settings.scss b/app/assets/stylesheets/pages/settings.scss
index c5f52377752..b82c638a5b7 100644
--- a/app/assets/stylesheets/pages/settings.scss
+++ b/app/assets/stylesheets/pages/settings.scss
@@ -183,7 +183,7 @@
.option-description,
.option-disabled-reason {
- margin-left: 30px;
+ margin-left: 20px;
color: $project-option-descr-color;
margin-top: -5px;
}
diff --git a/app/controllers/projects/metrics/dashboards/builder_controller.rb b/app/controllers/projects/metrics/dashboards/builder_controller.rb
index 8f0c963544c..a7832aa363e 100644
--- a/app/controllers/projects/metrics/dashboards/builder_controller.rb
+++ b/app/controllers/projects/metrics/dashboards/builder_controller.rb
@@ -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
diff --git a/app/finders/design_management/designs_finder.rb b/app/finders/design_management/designs_finder.rb
index 10f95520d1e..4a7a5734cd8 100644
--- a/app/finders/design_management/designs_finder.rb
+++ b/app/finders/design_management/designs_finder.rb
@@ -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
diff --git a/app/models/application_record.rb b/app/models/application_record.rb
index 9ec407a10a4..91b8bfedcbb 100644
--- a/app/models/application_record.rb
+++ b/app/models/application_record.rb
@@ -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
diff --git a/app/models/design_management/design.rb b/app/models/design_management/design.rb
index 0dca6333fa1..f60e79bec80 100644
--- a/app/models/design_management/design.rb
+++ b/app/models/design_management/design.rb
@@ -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
diff --git a/app/models/design_management/design_collection.rb b/app/models/design_management/design_collection.rb
index 18d1541e9c7..b8e408bcccc 100644
--- a/app/models/design_management/design_collection.rb
+++ b/app/models/design_management/design_collection.rb
@@ -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
diff --git a/app/services/metrics/dashboard/panel_preview_service.rb b/app/services/metrics/dashboard/panel_preview_service.rb
new file mode 100644
index 00000000000..5b24d817fb6
--- /dev/null
+++ b/app/services/metrics/dashboard/panel_preview_service.rb
@@ -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
diff --git a/app/views/admin/application_settings/ci/_header.html.haml b/app/views/admin/application_settings/ci/_header.html.haml
index 104c68a1e66..aa1a6256986 100644
--- a/app/views/admin/application_settings/ci/_header.html.haml
+++ b/app/views/admin/application_settings/ci/_header.html.haml
@@ -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')
diff --git a/app/views/ci/variables/_content.html.haml b/app/views/ci/variables/_content.html.haml
index 60106e8c530..bf695d871f8 100644
--- a/app/views/ci/variables/_content.html.haml
+++ b/app/views/ci/variables/_content.html.haml
@@ -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')
diff --git a/app/views/projects/pipelines/_with_tabs.html.haml b/app/views/projects/pipelines/_with_tabs.html.haml
index a86451b2022..bf17eba2e3f 100644
--- a/app/views/projects/pipelines/_with_tabs.html.haml
+++ b/app/views/projects/pipelines/_with_tabs.html.haml
@@ -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')
diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml
index 0b5700e5413..ea3ca278426 100644
--- a/app/views/shared/issuable/_search_bar.html.haml
+++ b/app/views/shared/issuable/_search_bar.html.haml
@@ -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
diff --git a/changelogs/unreleased/221043-enable-similarity-search-feature-flags-by-default.yml b/changelogs/unreleased/221043-enable-similarity-search-feature-flags-by-default.yml
new file mode 100644
index 00000000000..e5b694b7be2
--- /dev/null
+++ b/changelogs/unreleased/221043-enable-similarity-search-feature-flags-by-default.yml
@@ -0,0 +1,5 @@
+---
+title: Order projects within the project dropdown by relevance in analytics features
+merge_request: 38675
+author:
+type: changed
diff --git a/changelogs/unreleased/221167-model-changes.yml b/changelogs/unreleased/221167-model-changes.yml
new file mode 100644
index 00000000000..99d84ce117d
--- /dev/null
+++ b/changelogs/unreleased/221167-model-changes.yml
@@ -0,0 +1,5 @@
+---
+title: Add relative positioning on designs
+merge_request: 37835
+author:
+type: changed
diff --git a/changelogs/unreleased/223261-move-pipeline-header-to-graphql.yml b/changelogs/unreleased/223261-move-pipeline-header-to-graphql.yml
new file mode 100644
index 00000000000..43eceec845f
--- /dev/null
+++ b/changelogs/unreleased/223261-move-pipeline-header-to-graphql.yml
@@ -0,0 +1,5 @@
+---
+title: Take DAG view out of beta
+merge_request: 38517
+author:
+type: changed
diff --git a/changelogs/unreleased/233426-justin_ho-fix-svg-alignment-jobs-page.yml b/changelogs/unreleased/233426-justin_ho-fix-svg-alignment-jobs-page.yml
new file mode 100644
index 00000000000..74ab09e82d8
--- /dev/null
+++ b/changelogs/unreleased/233426-justin_ho-fix-svg-alignment-jobs-page.yml
@@ -0,0 +1,5 @@
+---
+title: Fix vertical alignment of svg icons on Jobs page
+merge_request: 38656
+author:
+type: fixed
diff --git a/changelogs/unreleased/migrate-btn-vul-charts.yml b/changelogs/unreleased/migrate-btn-vul-charts.yml
new file mode 100644
index 00000000000..b7e427277ae
--- /dev/null
+++ b/changelogs/unreleased/migrate-btn-vul-charts.yml
@@ -0,0 +1,5 @@
+---
+title: Button migration vulnerability charts
+merge_request: 38610
+author:
+type: changed
diff --git a/config/feature_flags/development/reorder_designs.yml b/config/feature_flags/development/reorder_designs.yml
new file mode 100644
index 00000000000..3bb05ff743f
--- /dev/null
+++ b/config/feature_flags/development/reorder_designs.yml
@@ -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
diff --git a/db/migrate/20200722091435_add_relative_position_to_design_management_designs.rb b/db/migrate/20200722091435_add_relative_position_to_design_management_designs.rb
new file mode 100644
index 00000000000..42b7a1d3898
--- /dev/null
+++ b/db/migrate/20200722091435_add_relative_position_to_design_management_designs.rb
@@ -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
diff --git a/db/migrate/20200728174137_add_index_on_design_management_designs_issue_id_and_relative_position_and_id.rb b/db/migrate/20200728174137_add_index_on_design_management_designs_issue_id_and_relative_position_and_id.rb
new file mode 100644
index 00000000000..140c344e837
--- /dev/null
+++ b/db/migrate/20200728174137_add_index_on_design_management_designs_issue_id_and_relative_position_and_id.rb
@@ -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
diff --git a/db/schema_migrations/20200722091435 b/db/schema_migrations/20200722091435
new file mode 100644
index 00000000000..13b3c855293
--- /dev/null
+++ b/db/schema_migrations/20200722091435
@@ -0,0 +1 @@
+d4e389b1469b968b703432de9ece6c362e45ec4ba3ad20d1f6c6418253969379 \ No newline at end of file
diff --git a/db/schema_migrations/20200728174137 b/db/schema_migrations/20200728174137
new file mode 100644
index 00000000000..7863b7eb40b
--- /dev/null
+++ b/db/schema_migrations/20200728174137
@@ -0,0 +1 @@
+a16e7fdcc62f39af3038317cb39ffb4c35f41ae45f5de429f18837309739110b \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 8fcc46d98a8..d3fbf7a202f 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -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);
diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql
index b178e866bd6..e0e3bfc22ce 100644
--- a/doc/api/graphql/reference/gitlab_schema.graphql
+++ b/doc/api/graphql/reference/gitlab_schema.graphql
@@ -2265,6 +2265,51 @@ enum DastScanTypeEnum {
}
"""
+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
"""
input DastScannerProfileCreateInput {
@@ -2315,6 +2360,21 @@ type DastScannerProfileCreatePayload {
}
"""
+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.
"""
type DastSiteProfile {
@@ -9688,6 +9748,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
"""
dastSiteProfiles(
diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json
index 469e3615746..3c237fbb5a4 100644
--- a/doc/api/graphql/reference/gitlab_schema.json
+++ b/doc/api/graphql/reference/gitlab_schema.json
@@ -6079,6 +6079,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",
"description": "Autogenerated input type of DastScannerProfileCreate",
@@ -6216,6 +6356,51 @@
},
{
"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",
"description": "Represents a DAST Site Profile.",
"fields": [
@@ -28994,6 +29179,59 @@
"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",
"args": [
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 3a9a6faf41b..0fb8156f805 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -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
diff --git a/doc/ci/directed_acyclic_graph/index.md b/doc/ci/directed_acyclic_graph/index.md
index b7dd74a0230..8fc58df51fe 100644
--- a/doc/ci/directed_acyclic_graph/index.md
+++ b/doc/ci/directed_acyclic_graph/index.md
@@ -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:
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index 2fde62b3c73..dbe662c3ab2 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -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:
diff --git a/doc/development/auto_devops.md b/doc/development/auto_devops.md
index 7d0c020ef96..6bdc77fff63 100644
--- a/doc/development/auto_devops.md
+++ b/doc/development/auto_devops.md
@@ -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
diff --git a/doc/development/chatops_on_gitlabcom.md b/doc/development/chatops_on_gitlabcom.md
index a3a4f1d7adc..0dd916c37fd 100644
--- a/doc/development/chatops_on_gitlabcom.md
+++ b/doc/development/chatops_on_gitlabcom.md
@@ -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.
diff --git a/doc/development/go_guide/dependencies.md b/doc/development/go_guide/dependencies.md
index b85344635c6..871d25ded24 100644
--- a/doc/development/go_guide/dependencies.md
+++ b/doc/development/go_guide/dependencies.md
@@ -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
diff --git a/doc/development/go_guide/index.md b/doc/development/go_guide/index.md
index 6954b2bd6dc..abbe264580d 100644
--- a/doc/development/go_guide/index.md
+++ b/doc/development/go_guide/index.md
@@ -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
diff --git a/doc/development/kubernetes.md b/doc/development/kubernetes.md
index 9e0e686f447..64089d51c7b 100644
--- a/doc/development/kubernetes.md
+++ b/doc/development/kubernetes.md
@@ -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
diff --git a/doc/operations/incident_management/status_page.md b/doc/operations/incident_management/status_page.md
index b253cf4f5a1..e376607d86f 100644
--- a/doc/operations/incident_management/status_page.md
+++ b/doc/operations/incident_management/status_page.md
@@ -8,127 +8,172 @@ 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)
-NOTE: **Note:**
-Only AWS S3 is supported as a deploy target.
+Clicking an incident displays a detail page with more information about a particular incident:
-```mermaid
-graph TB
- subgraph GitLab Instance
- issues(issue updates) -- trigger --> middleware(Background job: JSON generation)
- end
- subgraph Cloud Provider
- middleware --saves data --> c1(Cloud Bucket stores JSON file)
- end
- subgraph Status Page
- d(Static Site on CDN) -- fetches data --> c1
- end
-```
+![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
-Setting up a Status Page is pretty painless but there are a few things you need to do.
+To configure a GitLab Status Page you must:
-### Cloud account set up
+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).
-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.
+### Configure GitLab with cloud provider information
-#### AWS Setup
+To provide GitLab with the AWS account information needed to push content to your Status Page:
-1. Within your AWS acccout, create two new IAM policies.
+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.
-### Status Page project
-
-To deploy the Status Page to AWS S3 you need to add the Status Page project & configure the necessary CI variables.
+### Create a status page project
-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.
+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:
-### Syncing incidents to the Status Page
+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.
-Once the CI/CD variables are set, you'll need to set up the Project you want to use for Incident issues:
+ 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).
-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**.
+ - `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.
-## Status Page UI
+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).
-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.
+### Sync incidents to the Status Page
-![Status Page landing page](img/status_page_incidents_v12_10.png)
+After creating the CI/CD variables, configure the Project you want to use for
+Incident issues:
-### Incident detail page
+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**.
-The incident detail page shows detailed information about a particular incident. For example:
+## How to use your GitLab Status Page
-- 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.
+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:
-![Status Page detail](img/status_page_detail_v12_10.png)
+```mermaid
+graph TB
+ subgraph GitLab Instance
+ issues(issue updates) -- trigger --> middleware(Background job: JSON generation)
+ end
+ subgraph Cloud Provider
+ middleware --saves data --> c1(Cloud Bucket stores JSON file)
+ end
+ subgraph Status Page
+ d(Static Site on CDN) -- fetches data --> c1
+ end
+```
-## How it works
+### Publish an incident
-### Publishing Incidents
+To publish an incident:
-To publish an Incident, you first need to create an issue in the Project you enabled the Status Page settings in.
+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.
-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.
+ NOTE: **Note:**
+ Confidential issues can't be published.
-After the quick action is used, a background worker publishes the issue onto the Status Page using the credentials you provided during setup.
+A background worker publishes the issue onto the Status Page using the credentials
+you provided during setup. As part of publication, GitLab will:
-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.
+- 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).)
-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.
-
-### Changing 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.
-
-## Attachment storage
-
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/205166) in GitLab 13.1.
+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.
-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).
+### Update the incident status
-### Limit
+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.
-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.
diff --git a/doc/user/group/epics/index.md b/doc/user/group/epics/index.md
index a2b04e2d7fe..fa7914f02b1 100644
--- a/doc/user/group/epics/index.md
+++ b/doc/user/group/epics/index.md
@@ -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),
diff --git a/doc/user/project/settings/index.md b/doc/user/project/settings/index.md
index d104e0614ea..2753add507e 100644
--- a/doc/user/project/settings/index.md
+++ b/doc/user/project/settings/index.md
@@ -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).
diff --git a/lib/api/entities/feature.rb b/lib/api/entities/feature.rb
index 3c9182340ea..618a7be9c7b 100644
--- a/lib/api/entities/feature.rb
+++ b/lib/api/entities/feature.rb
@@ -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
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index 68cf1938b64..813e41b4d39 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -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 }
diff --git a/lib/api/helpers/pagination_strategies.rb b/lib/api/helpers/pagination_strategies.rb
index 823891d6fe7..61cff37e4ab 100644
--- a/lib/api/helpers/pagination_strategies.rb
+++ b/lib/api/helpers/pagination_strategies.rb
@@ -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
diff --git a/lib/api/project_snippets.rb b/lib/api/project_snippets.rb
index 09934502e85..fba4c60504f 100644
--- a/lib/api/project_snippets.rb
+++ b/lib/api/project_snippets.rb
@@ -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]
diff --git a/lib/backup/files.rb b/lib/backup/files.rb
index 5e784dadb14..dae9056a47b 100644
--- a/lib/backup/files.rb
+++ b/lib/backup/files.rb
@@ -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
diff --git a/lib/banzai/filter/gollum_tags_filter.rb b/lib/banzai/filter/gollum_tags_filter.rb
index 033e3d2c33e..7928272a2cf 100644
--- a/lib/banzai/filter/gollum_tags_filter.rb
+++ b/lib/banzai/filter/gollum_tags_filter.rb
@@ -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
diff --git a/lib/bitbucket_server/paginator.rb b/lib/bitbucket_server/paginator.rb
index 9eda1c921b2..8a494379864 100644
--- a/lib/bitbucket_server/paginator.rb
+++ b/lib/bitbucket_server/paginator.rb
@@ -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
diff --git a/lib/declarative_policy/runner.rb b/lib/declarative_policy/runner.rb
index 70666dc7924..59588b4d84e 100644
--- a/lib/declarative_policy/runner.rb
+++ b/lib/declarative_policy/runner.rb
@@ -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|
diff --git a/lib/gitlab/auth/ldap/adapter.rb b/lib/gitlab/auth/ldap/adapter.rb
index f64fcd822c6..4f448211abf 100644
--- a/lib/gitlab/auth/ldap/adapter.rb
+++ b/lib/gitlab/auth/ldap/adapter.rb
@@ -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
diff --git a/lib/gitlab/bare_repository_import/importer.rb b/lib/gitlab/bare_repository_import/importer.rb
index 144ba2ec031..ab7a08ffef9 100644
--- a/lib/gitlab/bare_repository_import/importer.rb
+++ b/lib/gitlab/bare_repository_import/importer.rb
@@ -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
diff --git a/lib/gitlab/ci/config/external/context.rb b/lib/gitlab/ci/config/external/context.rb
index 814dcc66362..cf6c2961ee7 100644
--- a/lib/gitlab/ci/config/external/context.rb
+++ b/lib/gitlab/ci/config/external/context.rb
@@ -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
diff --git a/lib/gitlab/ci/reports/accessibility_reports_comparer.rb b/lib/gitlab/ci/reports/accessibility_reports_comparer.rb
index fa6337166d5..210eb17f2d3 100644
--- a/lib/gitlab/ci/reports/accessibility_reports_comparer.rb
+++ b/lib/gitlab/ci/reports/accessibility_reports_comparer.rb
@@ -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
diff --git a/lib/gitlab/cycle_analytics/summary/value.rb b/lib/gitlab/cycle_analytics/summary/value.rb
index e443e037517..36306fa7c45 100644
--- a/lib/gitlab/cycle_analytics/summary/value.rb
+++ b/lib/gitlab/cycle_analytics/summary/value.rb
@@ -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
diff --git a/lib/gitlab/cycle_analytics/summary_helper.rb b/lib/gitlab/cycle_analytics/summary_helper.rb
index 3cf9f463024..11e48679a40 100644
--- a/lib/gitlab/cycle_analytics/summary_helper.rb
+++ b/lib/gitlab/cycle_analytics/summary_helper.rb
@@ -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)
diff --git a/lib/gitlab/danger/teammate.rb b/lib/gitlab/danger/teammate.rb
index dac61126488..9b389907090 100644
--- a/lib/gitlab/danger/teammate.rb
+++ b/lib/gitlab/danger/teammate.rb
@@ -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')
diff --git a/lib/gitlab/database.rb b/lib/gitlab/database.rb
index c2c9341ac02..859b53b9887 100644
--- a/lib/gitlab/database.rb
+++ b/lib/gitlab/database.rb
@@ -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?
diff --git a/lib/gitlab/database/connection_timer.rb b/lib/gitlab/database/connection_timer.rb
index ef8d52ba71c..f9b893ffd0f 100644
--- a/lib/gitlab/database/connection_timer.rb
+++ b/lib/gitlab/database/connection_timer.rb
@@ -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
diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb
index 91fb18fc434..a618a3017b2 100644
--- a/lib/gitlab/database/migration_helpers.rb
+++ b/lib/gitlab/database/migration_helpers.rb
@@ -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
diff --git a/lib/gitlab/exclusive_lease.rb b/lib/gitlab/exclusive_lease.rb
index 1d2c1c69423..b602393b59e 100644
--- a/lib/gitlab/exclusive_lease.rb
+++ b/lib/gitlab/exclusive_lease.rb
@@ -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
diff --git a/lib/gitlab/exclusive_lease_helpers/sleeping_lock.rb b/lib/gitlab/exclusive_lease_helpers/sleeping_lock.rb
index 8213c9bc042..52035220a71 100644
--- a/lib/gitlab/exclusive_lease_helpers/sleeping_lock.rb
+++ b/lib/gitlab/exclusive_lease_helpers/sleeping_lock.rb
@@ -39,7 +39,7 @@ module Gitlab
end
def first_attempt?
- attempts.zero?
+ attempts == 0
end
def sleep_sec
diff --git a/lib/gitlab/experimentation.rb b/lib/gitlab/experimentation.rb
index 135601bf483..2dd06504542 100644
--- a/lib/gitlab/experimentation.rb
+++ b/lib/gitlab/experimentation.rb
@@ -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?
diff --git a/lib/gitlab/file_hook.rb b/lib/gitlab/file_hook.rb
index f23ef2921d7..55eba2858fb 100644
--- a/lib/gitlab/file_hook.rb
+++ b/lib/gitlab/file_hook.rb
@@ -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
diff --git a/lib/gitlab/git/commit.rb b/lib/gitlab/git/commit.rb
index 8db73ecc480..0bc7ecccf5e 100644
--- a/lib/gitlab/git/commit.rb
+++ b/lib/gitlab/git/commit.rb
@@ -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
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 08c736c1331..596b4e9f692 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -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)
diff --git a/lib/gitlab/git/rugged_impl/blob.rb b/lib/gitlab/git/rugged_impl/blob.rb
index 5c73c0c66a9..dc869ff5279 100644
--- a/lib/gitlab/git/rugged_impl/blob.rb
+++ b/lib/gitlab/git/rugged_impl/blob.rb
@@ -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,
diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb
index 47eda5c741b..131c00db612 100644
--- a/lib/gitlab/gitaly_client.rb
+++ b/lib/gitlab/gitaly_client.rb
@@ -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
diff --git a/lib/gitlab/github_import/user_finder.rb b/lib/gitlab/github_import/user_finder.rb
index 9da986ae921..34d1231b9a5 100644
--- a/lib/gitlab/github_import/user_finder.rb
+++ b/lib/gitlab/github_import/user_finder.rb
@@ -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
diff --git a/lib/gitlab/hashed_storage/migrator.rb b/lib/gitlab/hashed_storage/migrator.rb
index 6a8e16f5a85..b72d08549fe 100644
--- a/lib/gitlab/hashed_storage/migrator.rb
+++ b/lib/gitlab/hashed_storage/migrator.rb
@@ -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
diff --git a/lib/gitlab/import_export/command_line_util.rb b/lib/gitlab/import_export/command_line_util.rb
index bdecff0931c..2f8769e261d 100644
--- a/lib/gitlab/import_export/command_line_util.rb
+++ b/lib/gitlab/import_export/command_line_util.rb
@@ -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
diff --git a/lib/gitlab/multi_collection_paginator.rb b/lib/gitlab/multi_collection_paginator.rb
index 33e0c6aa9b7..002171854ad 100644
--- a/lib/gitlab/multi_collection_paginator.rb
+++ b/lib/gitlab/multi_collection_paginator.rb
@@ -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
diff --git a/lib/gitlab/polling_interval.rb b/lib/gitlab/polling_interval.rb
index e286c3d467e..2c95a9e8d91 100644
--- a/lib/gitlab/polling_interval.rb
+++ b/lib/gitlab/polling_interval.rb
@@ -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
diff --git a/lib/gitlab/project_search_results.rb b/lib/gitlab/project_search_results.rb
index e6b25e71eb3..f8141278e48 100644
--- a/lib/gitlab/project_search_results.rb
+++ b/lib/gitlab/project_search_results.rb
@@ -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
diff --git a/lib/gitlab/seeder.rb b/lib/gitlab/seeder.rb
index d652719721e..e26d45e1b33 100644
--- a/lib/gitlab/seeder.rb
+++ b/lib/gitlab/seeder.rb
@@ -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} ⏰"
diff --git a/lib/gitlab/sidekiq_cluster.rb b/lib/gitlab/sidekiq_cluster.rb
index e74ae8d0f03..d05c717d2fa 100644
--- a/lib/gitlab/sidekiq_cluster.rb
+++ b/lib/gitlab/sidekiq_cluster.rb
@@ -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)
diff --git a/lib/gitlab/sidekiq_daemon/memory_killer.rb b/lib/gitlab/sidekiq_daemon/memory_killer.rb
index 9d0d67a488f..b8a4eedd620 100644
--- a/lib/gitlab/sidekiq_daemon/memory_killer.rb
+++ b/lib/gitlab/sidekiq_daemon/memory_killer.rb
@@ -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
diff --git a/lib/gitlab/sidekiq_middleware/memory_killer.rb b/lib/gitlab/sidekiq_middleware/memory_killer.rb
index 49c4fdc3033..0b38c98f710 100644
--- a/lib/gitlab/sidekiq_middleware/memory_killer.rb
+++ b/lib/gitlab/sidekiq_middleware/memory_killer.rb
@@ -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
diff --git a/lib/gitlab/sidekiq_status.rb b/lib/gitlab/sidekiq_status.rb
index 0dafccb3d34..2293e2adee1 100644
--- a/lib/gitlab/sidekiq_status.rb
+++ b/lib/gitlab/sidekiq_status.rb
@@ -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.
diff --git a/lib/gitlab/slash_commands/presenters/issue_show.rb b/lib/gitlab/slash_commands/presenters/issue_show.rb
index 448381b64ed..e9df015f249 100644
--- a/lib/gitlab/slash_commands/presenters/issue_show.rb
+++ b/lib/gitlab/slash_commands/presenters/issue_show.rb
@@ -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
diff --git a/lib/gitlab/task_helpers.rb b/lib/gitlab/task_helpers.rb
index 6ccb442b1e0..73187d8dea8 100644
--- a/lib/gitlab/task_helpers.rb
+++ b/lib/gitlab/task_helpers.rb
@@ -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
diff --git a/lib/gitlab/untrusted_regexp.rb b/lib/gitlab/untrusted_regexp.rb
index c237f4a7404..6a3e2062070 100644
--- a/lib/gitlab/untrusted_regexp.rb
+++ b/lib/gitlab/untrusted_regexp.rb
@@ -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
diff --git a/lib/gitlab/usage_data/topology.rb b/lib/gitlab/usage_data/topology.rb
index 9a1c03173eb..d7480a8c05e 100644
--- a/lib/gitlab/usage_data/topology.rb
+++ b/lib/gitlab/usage_data/topology.rb
@@ -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
diff --git a/lib/gitlab/utils.rb b/lib/gitlab/utils.rb
index c3c679e4311..e2d93e7cd29 100644
--- a/lib/gitlab/utils.rb
+++ b/lib/gitlab/utils.rb
@@ -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
diff --git a/lib/system_check/sidekiq_check.rb b/lib/system_check/sidekiq_check.rb
index 2f5fc2cea89..4f1533a5b7d 100644
--- a/lib/system_check/sidekiq_check.rb
+++ b/lib/system_check/sidekiq_check.rb
@@ -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 ... '
diff --git a/lib/tasks/gitlab/gitaly.rake b/lib/tasks/gitlab/gitaly.rake
index fc55d9704d1..74cf3aad951 100644
--- a/lib/tasks/gitlab/gitaly.rake
+++ b/lib/tasks/gitlab/gitaly.rake
@@ -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(
diff --git a/lib/tasks/gitlab/snippets.rake b/lib/tasks/gitlab/snippets.rake
index c391cecfdbc..ed2e88692d5 100644
--- a/lib/tasks/gitlab/snippets.rake
+++ b/lib/tasks/gitlab/snippets.rake
@@ -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"
diff --git a/lib/tasks/gitlab/workhorse.rake b/lib/tasks/gitlab/workhorse.rake
index 53343c8f8ff..15084a118b7 100644
--- a/lib/tasks/gitlab/workhorse.rake
+++ b/lib/tasks/gitlab/workhorse.rake
@@ -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])
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 986c5d78821..49d30286c4c 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -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 ""
diff --git a/spec/finders/design_management/designs_finder_spec.rb b/spec/finders/design_management/designs_finder_spec.rb
index 696327cc49c..0133095827d 100644
--- a/spec/finders/design_management/designs_finder_spec.rb
+++ b/spec/finders/design_management/designs_finder_spec.rb
@@ -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
diff --git a/spec/frontend/pipelines/components/dag/dag_spec.js b/spec/frontend/pipelines/components/dag/dag_spec.js
index 7dea6d819b9..7fd5a8e71e2 100644
--- a/spec/frontend/pipelines/components/dag/dag_spec.js
+++ b/spec/frontend/pipelines/components/dag/dag_spec.js
@@ -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);
});
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index f71c6e61c08..58cae672cf7 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -466,6 +466,7 @@ project:
- vulnerability_identifiers
- vulnerability_scanners
- dast_site_profiles
+- dast_scanner_profiles
- dast_sites
- operations_feature_flags
- operations_feature_flags_client
diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml
index 5d3d8d1d91e..7bdf6da87ae 100644
--- a/spec/lib/gitlab/import_export/safe_model_attributes.yml
+++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml
@@ -768,6 +768,7 @@ DesignManagement::Design:
- id
- project_id
- filename
+- relative_position
DesignManagement::Action:
- id
- event
diff --git a/spec/models/application_record_spec.rb b/spec/models/application_record_spec.rb
index cc314d9077d..2a982fb86f6 100644
--- a/spec/models/application_record_spec.rb
+++ b/spec/models/application_record_spec.rb
@@ -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
diff --git a/spec/models/design_management/design_collection_spec.rb b/spec/models/design_management/design_collection_spec.rb
index c5e290da759..a48dc457c38 100644
--- a/spec/models/design_management/design_collection_spec.rb
+++ b/spec/models/design_management/design_collection_spec.rb
@@ -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
diff --git a/spec/models/design_management/design_spec.rb b/spec/models/design_management/design_spec.rb
index 345147390c0..7251026cc51 100644
--- a/spec/models/design_management/design_spec.rb
+++ b/spec/models/design_management/design_spec.rb
@@ -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])
diff --git a/spec/requests/api/composer_packages_spec.rb b/spec/requests/api/composer_packages_spec.rb
index d756a7700f6..29372d15a32 100644
--- a/spec/requests/api/composer_packages_spec.rb
+++ b/spec/requests/api/composer_packages_spec.rb
@@ -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) }
-
- using RSpec::Parameterized::TableSyntax
+ context 'with valid project' do
+ let!(:package) { create(:composer_package, :with_metadatum, project: project) }
+
+ 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
- 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
+ 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
-
- it_behaves_like 'rejects Composer access with unknown group id'
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
-
- 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
+ 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
+ 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
-
- it_behaves_like 'rejects Composer access with unknown group id'
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
+ 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
+ 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
+ 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
-
- it_behaves_like 'rejects Composer access with unknown group id'
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
-
- 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
+ context 'with valid project' do
+ using RSpec::Parameterized::TableSyntax
- 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
+ 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" }
+ 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
+ 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
+ context 'with a match package name and sha' do
+ let(:branch) { project.repository.find_branch('master') }
+ let(:sha) { branch.target }
- with_them do
- let(:token) { user_token ? personal_access_token.token : 'wrong' }
- let(:headers) { user_role == :anonymous ? {} : build_basic_auth_header(user.username, token) }
+ 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
- before do
- project.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project_visibility_level, false))
- 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) }
- it_behaves_like 'process Composer api request', params[:user_role], params[:expected_status], params[:member]
+ 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 'rejects Composer access with unknown project id'
end
+
+ it_behaves_like 'rejects Composer access with unknown project id'
end
end
diff --git a/spec/requests/api/project_snippets_spec.rb b/spec/requests/api/project_snippets_spec.rb
index fbb0e3e109f..9b876edae24 100644
--- a/spec/requests/api/project_snippets_spec.rb
+++ b/spec/requests/api/project_snippets_spec.rb
@@ -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
+ context 'with an admin' 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(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 'creates a new snippet' do
+ subject
- it_behaves_like 'project snippet repository actions' do
- subject { post api("/projects/#{project.id}/snippets/", admin), params: params }
- end
+ 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
- it 'returns 400 for missing parameters' do
- params.delete(:title)
+ it_behaves_like 'project snippet repository actions'
- post api("/projects/#{project.id}/snippets/", admin), params: params
+ it 'returns 400 for missing parameters' do
+ params.delete(:title)
- expect(response).to have_gitlab_http_status(:bad_request)
- end
+ subject
- it 'returns 400 if content is blank' do
- params[:content] = ''
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
- post api("/projects/#{project.id}/snippets/", admin), params: params
+ it_behaves_like 'snippet creation with files parameter'
- expect(response).to have_gitlab_http_status(:bad_request)
- expect(json_response['error']).to eq 'content is empty'
- end
+ it_behaves_like 'snippet creation without files parameter'
- it 'returns 400 if title is blank' do
- params[:title] = ''
+ it 'returns 400 if title is blank' do
+ params[: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 'title is empty'
+ 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
diff --git a/spec/requests/api/snippets_spec.rb b/spec/requests/api/snippets_spec.rb
index df2372caf13..57131ed8c5b 100644
--- a/spec/requests/api/snippets_spec.rb
+++ b/spec/requests/api/snippets_spec.rb
@@ -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
diff --git a/spec/requests/projects/metrics/dashboards/builder_spec.rb b/spec/requests/projects/metrics/dashboards/builder_spec.rb
index b56a5def211..5f85788cdb6 100644
--- a/spec/requests/projects/metrics/dashboards/builder_spec.rb
+++ b/spec/requests/projects/metrics/dashboards/builder_spec.rb
@@ -6,6 +6,42 @@ RSpec.describe 'Projects::Metrics::Dashboards::BuilderController' do
let_it_be(:project) { create(:project) }
let_it_be(:environment) { create(:environment, project: project) }
let_it_be(:user) { create(:user) }
+ let_it_be(:valid_panel_yml) do
+ <<~YML
+ ---
+ title: "Super Chart A1"
+ type: "area-chart"
+ y_label: "y_label"
+ weight: 1
+ max_value: 1
+ metrics:
+ - id: metric_a1
+ query_range: |+
+ avg(
+ sum(
+ container_memory_usage_bytes{
+ container_name!="POD",
+ pod_name=~"^{{ci_environment_slug}}-(.*)",
+ namespace="{{kube_namespace}}",
+ user_def_variable="{{user_def_variable}}"
+ }
+ ) by (job)
+ ) without (job)
+ /1024/1024/1024
+ unit: unit
+ label: Legend Label
+ YML
+ end
+ let_it_be(:invalid_panel_yml) do
+ <<~YML
+ ---
+ title: "Super Chart A1"
+ type: "area-chart"
+ y_label: "y_label"
+ weight: 1
+ max_value: 1
+ YML
+ end
def send_request(params = {})
post namespace_project_metrics_dashboards_builder_path(namespace_id: project.namespace, project_id: project, format: :json, **params)
@@ -17,14 +53,14 @@ RSpec.describe 'Projects::Metrics::Dashboards::BuilderController' do
stub_feature_flags(metrics_dashboard_new_panel_page: true)
end
- it 'redirects to sign in' do
+ it 'redirects user to sign in page' do
send_request
expect(response).to redirect_to(new_user_session_path)
end
end
- context 'as user with reporter access' do
+ context 'as user with guest access' do
before do
stub_feature_flags(metrics_dashboard_new_panel_page: true)
project.add_guest(user)
@@ -49,10 +85,31 @@ RSpec.describe 'Projects::Metrics::Dashboards::BuilderController' do
stub_feature_flags(metrics_dashboard_new_panel_page: true)
end
- it 'returns success' do
- send_request
+ context 'valid yaml panel is supplied' do
+ it 'returns success' do
+ send_request(panel_yaml: valid_panel_yml)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to include('title' => 'Super Chart A1', 'type' => 'area-chart')
+ end
+ end
+
+ context 'invalid yaml panel is supplied' do
+ it 'returns unprocessable entity' do
+ send_request(panel_yaml: invalid_panel_yml)
+
+ expect(response).to have_gitlab_http_status(:unprocessable_entity)
+ expect(json_response['message']).to eq('Each "panel" must define an array :metrics')
+ end
+ end
+
+ context 'invalid panel_yaml is not a yaml string' do
+ it 'returns unprocessable entity' do
+ send_request(panel_yaml: 1)
- expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to have_gitlab_http_status(:unprocessable_entity)
+ expect(json_response['message']).to eq('Invalid configuration format')
+ end
end
end
diff --git a/spec/services/metrics/dashboard/panel_preview_service_spec.rb b/spec/services/metrics/dashboard/panel_preview_service_spec.rb
new file mode 100644
index 00000000000..d58dee3e7a3
--- /dev/null
+++ b/spec/services/metrics/dashboard/panel_preview_service_spec.rb
@@ -0,0 +1,83 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Metrics::Dashboard::PanelPreviewService do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:environment) { create(:environment, project: project) }
+ let_it_be(:panel_yml) do
+ <<~YML
+ ---
+ title: test panel
+ YML
+ end
+ let_it_be(:dashboard) do
+ {
+ panel_groups: [
+ {
+ panels: [{ 'title' => 'test panel' }]
+ }
+ ]
+ }
+ end
+
+ describe '#execute' do
+ subject(:service_response) { described_class.new(project, panel_yml, environment).execute }
+
+ context "valid panel's yaml" do
+ before do
+ allow_next_instance_of(::Gitlab::Metrics::Dashboard::Processor) do |processor|
+ allow(processor).to receive(:process).and_return(dashboard)
+ end
+ end
+
+ it 'returns success service response' do
+ expect(service_response.success?).to be_truthy
+ end
+
+ it 'returns processed panel' do
+ expect(service_response.payload).to eq('title' => 'test panel')
+ end
+
+ it 'uses dashboard processor' do
+ 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
+ ]
+ processor_params = [project, dashboard, sequence, environment: environment]
+
+ expect_next_instance_of(::Gitlab::Metrics::Dashboard::Processor, *processor_params) do |processor|
+ expect(processor).to receive(:process).and_return(dashboard)
+ end
+
+ service_response
+ end
+ end
+
+ context "invalid panel's yaml" do
+ [
+ Gitlab::Metrics::Dashboard::Errors::DashboardProcessingError,
+ Gitlab::Config::Loader::Yaml::NotHashError,
+ Gitlab::Config::Loader::Yaml::DataTooLargeError,
+ Gitlab::Config::Loader::FormatError
+ ].each do |error_class|
+ before do
+ allow_next_instance_of(::Gitlab::Metrics::Dashboard::Processor) do |processor|
+ allow(processor).to receive(:process).and_raise(error_class.new('error'))
+ end
+ end
+
+ it 'returns error service response' do
+ expect(service_response.error?).to be_truthy
+ end
+
+ it 'returns error message' do
+ expect(service_response.message).to eq('error')
+ end
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/requests/snippet_shared_examples.rb b/spec/support/shared_examples/requests/snippet_shared_examples.rb
index 644abb191a6..a17163328f4 100644
--- a/spec/support/shared_examples/requests/snippet_shared_examples.rb
+++ b/spec/support/shared_examples/requests/snippet_shared_examples.rb
@@ -106,3 +106,80 @@ RSpec.shared_examples 'snippet_multiple_files feature disabled' do
expect(json_response).not_to have_key('files')
end
end
+
+RSpec.shared_examples 'snippet creation 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
+end
+
+RSpec.shared_examples 'snippet creation 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
+
+ it 'returns 400 if content is blank' do
+ params[:content] = ''
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response['error']).to eq 'content is empty'
+ end
+end