Commit 0a6ffb54 authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent a0482614
......@@ -101,7 +101,7 @@ gem 'hashie-forbidden_attributes'
gem 'kaminari', '~> 1.0'
# HAML
gem 'hamlit', '~> 2.8.8'
gem 'hamlit', '~> 2.10.0'
# Files attachments
gem 'carrierwave', '~> 1.3'
......@@ -135,7 +135,7 @@ gem 'aws-sdk'
gem 'faraday_middleware-aws-signers-v4'
# Markdown and HTML processing
gem 'html-pipeline', '~> 2.8'
gem 'html-pipeline', '~> 2.12'
gem 'deckar01-task_list', '2.2.1'
gem 'gitlab-markup', '~> 1.7.0'
gem 'github-markup', '~> 1.7.0', require: 'github/markup'
......@@ -373,7 +373,7 @@ group :development, :test do
gem 'rubocop-rspec', '~> 1.22.1'
gem 'scss_lint', '~> 0.56.0', require: false
gem 'haml_lint', '~> 0.31.0', require: false
gem 'haml_lint', '~> 0.34.0', require: false
gem 'simplecov', '~> 0.16.1', require: false
gem 'bundler-audit', '~> 0.5.0', require: false
......
......@@ -460,17 +460,16 @@ GEM
guard (~> 2.1)
guard-compat (~> 1.1)
rspec (>= 2.99.0, < 4.0)
haml (5.0.4)
haml (5.1.2)
temple (>= 0.8.0)
tilt
haml_lint (0.31.0)
haml (>= 4.0, < 5.1)
haml_lint (0.34.0)
haml (>= 4.0, < 5.2)
rainbow
rake (>= 10, < 13)
rubocop (>= 0.50.0)
sysexits (~> 1.1)
hamlit (2.8.8)
temple (>= 0.8.0)
hamlit (2.10.0)
temple (>= 0.8.2)
thor
tilt
hangouts-chat (0.0.5)
......@@ -484,7 +483,7 @@ GEM
hipchat (1.5.2)
httparty
mimemagic
html-pipeline (2.8.4)
html-pipeline (2.12.2)
activesupport (>= 2)
nokogiri (>= 1.4)
html2text (0.2.0)
......@@ -1017,7 +1016,7 @@ GEM
sys-filesystem (1.1.6)
ffi
sysexits (1.2.0)
temple (0.8.1)
temple (0.8.2)
terminal-table (1.8.0)
unicode-display_width (~> 1.1, >= 1.1.1)
test-prof (0.10.0)
......@@ -1219,13 +1218,13 @@ DEPENDENCIES
grpc (~> 1.24.0)
gssapi
guard-rspec
haml_lint (~> 0.31.0)
hamlit (~> 2.8.8)
haml_lint (~> 0.34.0)
hamlit (~> 2.10.0)
hangouts-chat (~> 0.0.5)
hashie-forbidden_attributes
health_check (~> 2.6.0)
hipchat (~> 1.5.0)
html-pipeline (~> 2.8)
html-pipeline (~> 2.12)
html2text
httparty (~> 0.16.4)
icalendar
......
This diff is collapsed.
......@@ -169,12 +169,6 @@ export default {
<p v-if="shouldShowMetricsUnavailable" class="usage-info js-usage-info usage-info-unavailable">
{{ s__('mrWidget|Deployment statistics are not available currently') }}
</p>
<memory-graph
v-if="shouldShowMemoryGraph"
:metrics="memoryMetrics"
:deployment-time="deploymentTime"
height="25"
width="100"
/>
<memory-graph v-if="shouldShowMemoryGraph" :metrics="memoryMetrics" :height="25" :width="110" />
</div>
</template>
<script>
import { __, sprintf } from '~/locale';
import { getTimeago } from '../../lib/utils/datetime_utility';
import { formatDate, secondsToMilliseconds } from '~/lib/utils/datetime_utility';
import { GlSparklineChart } from '@gitlab/ui/dist/charts';
export default {
name: 'MemoryGraph',
components: {
GlSparklineChart,
},
props: {
metrics: { type: Array, required: true },
deploymentTime: { type: Number, required: true },
width: { type: String, required: true },
height: { type: String, required: true },
},
data() {
return {
pathD: '',
pathViewBox: '',
dotX: '',
dotY: '',
};
width: { type: Number, required: true },
height: { type: Number, required: true },
},
computed: {
getFormattedMedian() {
const deployedSince = getTimeago().format(this.deploymentTime * 1000);
return sprintf(__('Deployed %{deployedSince}'), { deployedSince });
chartData() {
return this.metrics.map(([x, y]) => [
this.getFormattedDeploymentTime(x),
this.getMemoryUsage(y),
]);
},
},
mounted() {
this.renderGraph(this.deploymentTime, this.metrics);
},
methods: {
/**
* Returns metric value index in metrics array
* with timestamp closest to matching median
*/
getMedianMetricIndex(median, metrics) {
let matchIndex = 0;
let timestampDiff = 0;
let smallestDiff = 0;
const metricTimestamps = metrics.map(v => v[0]);
// Find metric timestamp which is closest to deploymentTime
timestampDiff = Math.abs(metricTimestamps[0] - median);
metricTimestamps.forEach((timestamp, index) => {
if (index === 0) {
// Skip first element
return;
}
smallestDiff = Math.abs(timestamp - median);
if (smallestDiff < timestampDiff) {
matchIndex = index;
timestampDiff = smallestDiff;
}
});
return matchIndex;
getFormattedDeploymentTime(timestamp) {
return formatDate(new Date(secondsToMilliseconds(timestamp)), 'mmm dd yyyy HH:MM:s');
},
/**
* Get Graph Plotting values to render Line and Dot
*/
getGraphPlotValues(median, metrics) {
const renderData = metrics.map(v => v[1]);
const medianMetricIndex = this.getMedianMetricIndex(median, metrics);
let cx = 0;
let cy = 0;
// Find Maximum and Minimum values from `renderData` array
const maxMemory = Math.max.apply(null, renderData);
const minMemory = Math.min.apply(null, renderData);
// Find difference between extreme ends
const diff = maxMemory - minMemory;
const lineWidth = renderData.length;
// Iterate over metrics values and perform following
// 1. Find x & y co-ords for deploymentTime's memory value
// 2. Return line path against maxMemory
const linePath = renderData.map((y, x) => {
if (medianMetricIndex === x) {
cx = x;
cy = maxMemory - y;
}
return `${x} ${maxMemory - y}`;
});
return {
pathD: linePath,
pathViewBox: {
lineWidth,
diff,
},
dotX: cx,
dotY: cy,
};
},
/**
* Render Graph based on provided median and metrics values
*/
renderGraph(median, metrics) {
const { pathD, pathViewBox, dotX, dotY } = this.getGraphPlotValues(median, metrics);
// Set props and update graph on UI.
this.pathD = `M ${pathD}`;
this.pathViewBox = `0 0 ${pathViewBox.lineWidth} ${pathViewBox.diff}`;
this.dotX = dotX;
this.dotY = dotY;
getMemoryUsage(MBs) {
return Number(MBs).toFixed(2);
},
},
};
</script>
<template>
<div class="memory-graph-container">
<svg
:title="getFormattedMedian"
:width="width"
<div class="memory-graph-container p-1" :style="{ width: `${width}px` }">
<gl-sparkline-chart
:height="height"
class="has-tooltip"
xmlns="http://www.w3.org/2000/svg"
>
<path :d="pathD" :viewBox="pathViewBox" />
<circle :cx="dotX" :cy="dotY" r="1.5" transform="translate(0 -1)" />
</svg>
:tooltip-label="__('MB')"
:show-last-y-value="false"
:data="chartData"
/>
</div>
</template>
.memory-graph-container {
svg {
background: $white-light;
border: 1px solid $gray-200;
}
path {
fill: none;
stroke: $blue-500;
stroke-width: 2px;
}
circle {
stroke: $blue-700;
fill: $blue-700;
stroke-width: 4px;
}
background: $white-light;
border: 1px solid $gray-200;
}
......@@ -949,7 +949,6 @@
.deployment-info {
flex: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
min-width: 100px;
......
......@@ -111,7 +111,7 @@ class Projects::ErrorTrackingController < Projects::ApplicationController
end
def list_issues_params
params.permit(:search_term)
params.permit([:search_term, :sort])
end
def list_projects_params
......
# frozen_string_literal: true
module Mutations
module Todos
class Restore < ::Mutations::Todos::Base
graphql_name 'TodoRestore'
authorize :update_todo
argument :id,
GraphQL::ID_TYPE,
required: true,
description: 'The global id of the todo to restore'
field :todo, Types::TodoType,
null: false,
description: 'The requested todo'
def resolve(id:)
todo = authorized_find!(id: id)
restore(todo.id) if todo.done?
{
todo: todo.reset,
errors: errors_on_object(todo)
}
end
private
def restore(id)
TodoService.new.mark_todos_as_pending_by_ids([id], current_user)
end
end
end
end
......@@ -21,6 +21,7 @@ module Types
mount_mutation Mutations::Notes::Update
mount_mutation Mutations::Notes::Destroy
mount_mutation Mutations::Todos::MarkDone
mount_mutation Mutations::Todos::Restore
end
end
......
......@@ -7,7 +7,7 @@ module Clusters
REPOSITORY = 'https://storage.googleapis.com/triggermesh-charts'
METRICS_CONFIG = 'https://storage.googleapis.com/triggermesh-charts/istio-metrics.yaml'
FETCH_IP_ADDRESS_DELAY = 30.seconds
API_RESOURCES_PATH = 'config/knative/api_resources.yml'
API_GROUPS_PATH = 'config/knative/api_groups.yml'
self.table_name = 'clusters_applications_knative'
......@@ -109,15 +109,15 @@ module Clusters
end
def delete_knative_and_istio_crds
api_resources.map do |crd|
Gitlab::Kubernetes::KubectlCmd.delete("--ignore-not-found", "crd", "#{crd}")
api_groups.map do |group|
Gitlab::Kubernetes::KubectlCmd.delete_crds_from_group(group)
end
end
# returns an array of CRDs to be postdelete since helm does not
# manage the CRDs it creates.
def api_resources
@api_resources ||= YAML.safe_load(File.read(Rails.root.join(API_RESOURCES_PATH)))
def api_groups
@api_groups ||= YAML.safe_load(File.read(Rails.root.join(API_GROUPS_PATH)))
end
def install_knative_metrics
......
......@@ -5,6 +5,7 @@ module ErrorTracking
include Gitlab::Utils::StrongMemoize
include ReactiveCaching
SENTRY_API_ERROR_TYPE_BAD_REQUEST = 'bad_request_for_sentry_api'
SENTRY_API_ERROR_TYPE_MISSING_KEYS = 'missing_keys_in_sentry_response'
SENTRY_API_ERROR_TYPE_NON_20X_RESPONSE = 'non_20x_response_from_sentry'
SENTRY_API_ERROR_INVALID_SIZE = 'invalid_size_of_sentry_response'
......@@ -119,6 +120,8 @@ module ErrorTracking
{ error: e.message, error_type: SENTRY_API_ERROR_TYPE_MISSING_KEYS }
rescue Sentry::Client::ResponseInvalidSizeError => e
{ error: e.message, error_type: SENTRY_API_ERROR_INVALID_SIZE }
rescue Sentry::Client::BadRequestError => e
{ error: e.message, error_type: SENTRY_API_ERROR_TYPE_BAD_REQUEST }
end
# http://HOST/api/0/projects/ORG/PROJECT
......
......@@ -4,6 +4,7 @@ module ErrorTracking
class ListIssuesService < ErrorTracking::BaseService
DEFAULT_ISSUE_STATUS = 'unresolved'
DEFAULT_LIMIT = 20
DEFAULT_SORT = 'last_seen'
def execute
return error('Error Tracking is not enabled') unless enabled?
......@@ -12,7 +13,8 @@ module ErrorTracking
result = project_error_tracking_setting.list_sentry_issues(
issue_status: issue_status,
limit: limit,
search_term: search_term
search_term: search_term,
sort: sort
)
# our results are not yet ready
......@@ -33,10 +35,6 @@ module ErrorTracking
private
def fetch
project_error_tracking_setting.list_sentry_issues(issue_status: issue_status, limit: limit)
end
def parse_response(response)
{ issues: response[:issues] }
end
......@@ -60,5 +58,9 @@ module ErrorTracking
def can_read?
can?(current_user, :read_sentry_issue, project)
end
def sort
params[:sort] || DEFAULT_SORT
end
end
end
---
title: Improve sparkline chart in MR widget deployment
merge_request: 20085
author:
type: other
---
title: Add GraphQL mutation to restore a Todo
merge_request: 20261
author:
type: added
---
title: Add sort param to error tracking issue index
merge_request: 20101
author:
type: changed
---
title: Drop deprecated column from projects table
merge_request: 18914
author:
type: deprecated
---
title: replace var gl_dropdown.js
merge_request: 20166
author: nuwe1
type: other
module Hamlit
class TemplateHandler
def call(template)
Engine.new(
generator: Temple::Generators::RailsOutputBuffer,
attr_quote: '"'
).call(template.source)
end
end
end
ActionView::Template.register_template_handler(
:haml,
Hamlit::TemplateHandler.new
)
Hamlit::RailsTemplate.set_options(attr_quote: '"')
Hamlit::Filters.remove_filter('coffee')
Hamlit::Filters.remove_filter('coffeescript')
---
- networking.istio.io
- rbac.istio.io
- authentication.istio.io
- config.istio.io
- networking.internal.knative.dev
- serving.knative.dev
- caching.internal.knative.dev
- autoscaling.internal.knative.dev
\ No newline at end of file
---
- meshpolicies.authentication.istio.io
- policies.authentication.istio.io
- adapters.config.istio.io
- apikeys.config.istio.io
- attributemanifests.config.istio.io
- authorizations.config.istio.io
- bypasses.config.istio.io
- podautoscalers.autoscaling.internal.knative.dev
- builds.build.knative.dev
- buildtemplates.build.knative.dev
- clusterbuildtemplates.build.knative.dev
- images.caching.internal.knative.dev
- certificates.networking.internal.knative.dev
- clusteringresses.networking.internal.knative.dev
- serverlessservices.networking.internal.knative.dev
- configurations.serving.knative.dev
- revisions.serving.knative.dev
- routes.serving.knative.dev
- services.serving.knative.dev
- checknothings.config.istio.io
- circonuses.config.istio.io
- deniers.config.istio.io
- edges.config.istio.io
- fluentds.config.istio.io
- handlers.config.istio.io
- httpapispecbindings.config.istio.io
- httpapispecs.config.istio.io
- instances.config.istio.io
- kubernetesenvs.config.istio.io
- kuberneteses.config.istio.io
- listcheckers.config.istio.io
- listentries.config.istio.io
- logentries.config.istio.io
- memquotas.config.istio.io
- metrics.config.istio.io
- noops.config.istio.io
- opas.config.istio.io
- prometheuses.config.istio.io
- quotas.config.istio.io
- quotaspecbindings.config.istio.io
- quotaspecs.config.istio.io
- rbacs.config.istio.io
- redisquotas.config.istio.io
- reportnothings.config.istio.io
- rules.config.istio.io
- servicecontrolreports.config.istio.io
- servicecontrols.config.istio.io
- signalfxs.config.istio.io
- solarwindses.config.istio.io
- stackdrivers.config.istio.io
- statsds.config.istio.io
- stdios.config.istio.io
- templates.config.istio.io
- tracespans.config.istio.io
- destinationrules.networking.istio.io
- envoyfilters.networking.istio.io
- gateways.networking.istio.io
- serviceentries.networking.istio.io
- virtualservices.networking.istio.io
- rbacconfigs.rbac.istio.io
- servicerolebindings.rbac.istio.io
- serviceroles.rbac.istio.io
- cloudwatches.config.istio.io
- clusterrbacconfigs.rbac.istio.io
- dogstatsds.config.istio.io
- ingresses.networking.internal.knative.dev
- sidecars.networking.istio.io
- zipkins.config.istio.io
require './spec/support/sidekiq'
require './spec/support/sidekiq_middleware'
Gitlab::Seeder.quiet do
User.create!(
......
require './spec/support/sidekiq'
require './spec/support/sidekiq_middleware'
class Gitlab::Seeder::Projects
include ActionView::Helpers::NumberHelper
......
require './spec/support/sidekiq'
require './spec/support/sidekiq_middleware'
Sidekiq::Testing.inline! do
Gitlab::Seeder.quiet do
......
require './spec/support/sidekiq'
require './spec/support/sidekiq_middleware'
Gitlab::Seeder.quiet do
Project.not_mass_generated.each do |project|
......
require './spec/support/sidekiq'
require './spec/support/sidekiq_middleware'
Gitlab::Seeder.quiet do
Rake::Task["gitlab:seed:issues"].invoke
......
require './spec/support/sidekiq'
require './spec/support/sidekiq_middleware'
Gitlab::Seeder.quiet do
# Limit the number of merge requests per project to avoid long seeds
......
require './spec/support/sidekiq'
require './spec/support/sidekiq_middleware'
# Creating keys runs a gitlab-shell worker. Since we may not have the right
......
require './spec/support/sidekiq'
require './spec/support/sidekiq_middleware'
Gitlab::Seeder.quiet do
content =<<eos
......
require './spec/support/sidekiq'
require './spec/support/sidekiq_middleware'
Gitlab::Seeder.quiet do
Issue.find_each do |issue|
......
require './spec/support/sidekiq'
require './spec/support/sidekiq_middleware'
class Gitlab::Seeder::Pipelines
STAGES = %w[build test deploy notify]
......
require './spec/support/sidekiq'
require './spec/support/sidekiq_middleware'
Gitlab::Seeder.quiet do
EMOJI = Gitlab::Emoji.emojis.keys
......
require './spec/support/sidekiq'
require './spec/support/sidekiq_middleware'
Gitlab::Seeder.quiet do
admin_user = User.find(1)
......
require './spec/support/sidekiq'
require './spec/support/sidekiq_middleware'
require './spec/support/helpers/test_env'
class Gitlab::Seeder::CycleAnalytics
......
require './spec/support/sidekiq'
require './spec/support/sidekiq_middleware'
class Gitlab::Seeder::Environments
def initialize(project)
......
require './spec/support/sidekiq'
require './spec/support/sidekiq_middleware'
Sidekiq::Testing.inline! do
Gitlab::Seeder.quiet do
......
require './spec/support/sidekiq'
require './spec/support/sidekiq_middleware'
Sidekiq::Testing.inline! do
Gitlab::Seeder.quiet do
......
# frozen_string_literal: true
require './spec/support/sidekiq'
require './spec/support/sidekiq_middleware'
# Create an api access token for root user with the value: ypCa3Dzb23o5nvsixwPA
Gitlab::Seeder.quiet do
......
# frozen_string_literal: true
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class DropMergeRequestsRequireCodeOwnerApprovalFromProjects < ActiveRecord::Migration[5.2]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
remove_column :projects, :merge_requests_require_code_owner_approval, :boolean
end
def down
add_column :projects, :merge_requests_require_code_owner_approval, :boolean
add_concurrent_index(
:projects,
%i[archived pending_delete merge_requests_require_code_owner_approval],
name: 'projects_requiring_code_owner_approval',
where: '((pending_delete = false) AND (archived = false) AND (merge_requests_require_code_owner_approval = true))'
)
end
end
......@@ -3186,7 +3186,6 @@ ActiveRecord::Schema.define(version: 2019_11_18_182722) do
t.bigint "pool_repository_id"
t.string "runners_token_encrypted"
t.string "bfg_object_map"
t.boolean "merge_requests_require_code_owner_approval"
t.boolean "detected_repository_languages"
t.boolean "merge_requests_disable_committers_approval"
t.boolean "require_password_to_approve"
......@@ -3198,7 +3197,6 @@ ActiveRecord::Schema.define(version: 2019_11_18_182722) do
t.date "marked_for_deletion_at"
t.integer "marked_for_deletion_by_user_id"
t.index "lower((name)::text)", name: "index_projects_on_lower_name"
t.index ["archived", "pending_delete", "merge_requests_require_code_owner_approval"], name: "projects_requiring_code_owner_approval", where: "((pending_delete = false) AND (archived = false) AND (merge_requests_require_code_owner_approval = true))"
t.index ["created_at", "id"], name: "index_projects_on_created_at_and_id"
t.index ["creator_id"], name: "index_projects_on_creator_id"
t.index ["description"], name: "index_projects_on_description_trigram", opclass: :gin_trgm_ops, using: :gin
......
......@@ -4,6 +4,10 @@ type: reference
# Smartcard authentication **(PREMIUM ONLY)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/33669) in GitLab 12.6,
if a user has a pre-existing username and password, they can still use that to log
in by default. However, this can be disabled.
GitLab supports authentication using smartcards.
## Authentication methods
......
......@@ -3519,6 +3519,7 @@ type Mutation {
mergeRequestSetWip(input: MergeRequestSetWipInput!): MergeRequestSetWipPayload
removeAwardEmoji(input: RemoveAwardEmojiInput!): RemoveAwardEmojiPayload
todoMarkDone(input: TodoMarkDoneInput!): TodoMarkDonePayload
todoRestore(input: TodoRestoreInput!): TodoRestorePayload
toggleAwardEmoji(input: ToggleAwardEmojiInput!): ToggleAwardEmojiPayload
updateEpic(input: UpdateEpicInput!): UpdateEpicPayload
updateNote(input: UpdateNoteInput!): UpdateNotePayload
......@@ -4992,6 +4993,41 @@ type TodoMarkDonePayload {
todo: Todo!
}
"""
Autogenerated input type of TodoRestore
"""
input TodoRestoreInput {
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
"""
The global id of the todo to restore
"""
id: ID!
}
"""
Autogenerated return type of TodoRestore
"""
type TodoRestorePayload {
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
"""
Reasons why the mutation failed.
"""
errors: [String!]!
"""
The requested todo
"""
todo: Todo!
}
enum TodoStateEnum {
done
pending
......
......@@ -14328,6 +14328,33 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "todoRestore",
"description": null,
"args": [
{
"name": "input",
"description": null,
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "INPUT_OBJECT",
"name": "TodoRestoreInput",
"ofType": null
}
},
"defaultValue": null
}
],
"type": {
"kind": "OBJECT",
"name": "TodoRestorePayload",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "toggleAwardEmoji",
"description": null,
......@@ -16692,6 +16719,112 @@
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "TodoRestorePayload",
"description": "Autogenerated return type of TodoRestore",
"fields": [
{
"name": "clientMutationId",
"description": "A unique identifier for the client performing the mutation.",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "errors",
"description": "Reasons why the mutation failed.",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
}
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "todo",
"description": "The requested todo",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "Todo",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "INPUT_OBJECT",
"name": "TodoRestoreInput",
"description": "Autogenerated input type of TodoRestore",
"fields": null,
"inputFields": [
{
"name": "id",
"description": "The global id of the todo to restore",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "clientMutationId",
"description": "A unique identifier for the client performing the mutation.",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
}
],
"interfaces": null,
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "DesignManagementUploadPayload",
......
......@@ -777,6 +777,14 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph
| `errors` | String! => Array | Reasons why the mutation failed. |
| `todo` | Todo! | The requested todo |
### TodoRestorePayload
| Name | Type | Description |
| --- | ---- | ---------- |
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
| `errors` | String! => Array | Reasons why the mutation failed. |
| `todo` | Todo! | The requested todo |
### ToggleAwardEmojiPayload
| Name | Type | Description |
......
......@@ -1407,7 +1407,7 @@ If the merge request is already merged or closed - you get `405` and error messa
In case the merge request is not set to be merged when the pipeline succeeds, you'll also get a `406` error.
```
PUT /projects/:id/merge_requests/:merge_request_iid/cancel_merge_when_pipeline_succeeds
POST /projects/:id/merge_requests/:merge_request_iid/cancel_merge_when_pipeline_succeeds
```
Parameters:
......
......@@ -5,7 +5,7 @@ This document will guide you through adding another [package management system](
See already supported package types in [Packages documentation](../administration/packages/index.md)
Since GitLab packages' UI is pretty generic, it is possible to add basic new
package system support by solely backend changes. This guide is superficial and does
package system support with solely backend changes. This guide is superficial and does
not cover the way the code should be written. However, you can find a good example
by looking at existing merge requests with Maven and NPM support:
......@@ -14,6 +14,35 @@ by looking at existing merge requests with Maven and NPM support:
- [Maven repository](https://gitlab.com/gitlab-org/gitlab/merge_requests/6607).
- [Instance level endpoint for Maven repository](https://gitlab.com/gitlab-org/gitlab/merge_requests/8757)
## Suggested contributions
The goal of the Package group is to build a set of features that, within three years, will allow ninety percent of our customers to store all of their packages in GitLab. To do that we need to ensure that we support the below package manager formats.
| Format | Use case |
| ------ | ------ |
| [Bower](https://gitlab.com/gitlab-org/gitlab/issues/36888) | Boost your front end development by hosting your own Bower components. |
| [Chef](https://gitlab.com/gitlab-org/gitlab/issues/36889) | Configuration management with Chef using all the benefits of a repository manager. |
| [CocoaPods](https://gitlab.com/gitlab-org/gitlab/issues/36890) | Speed up development with Xcode and CocoaPods. |
| [Conan](https://docs.gitlab.com/ee/user/packages/conan_repository/) *12.6+* | A standardized way to share and version control C/C++ libraries across projects. |
| [Conda](https://gitlab.com/gitlab-org/gitlab/issues/36891) | Secure and private local Conda repositories. |
| [CRAN](https://gitlab.com/gitlab-org/gitlab/issues/36892) | Deploy and resolve CRAN packages for the R language. |
| [Debian](https://gitlab.com/gitlab-org/gitlab/issues/5835) | Host and provision Debian packages. |
| [Docker](https://docs.gitlab.com/ee/user/packages/container_registry/) *8.8+* | Host your own secure private Docker registries and proxy external Docker registries such as Docker Hub. |
| [Go](https://gitlab.com/gitlab-org/gitlab/issues/9773) | Resolve Go dependencies from and publish your Go packages to GitLab. |
| [Helm](https://gitlab.com/gitlab-org/gitlab/issues/18997) | Manage your Helm Charts in GitLab and gain control over deployments to your Kubernetes cluster. |
| [Maven](https://docs.gitlab.com/ee/user/packages/maven_repository/index.html) *11.3+*| The GitLab Maven Repository enables every project in GitLab to have its own space to store Maven packages. |
| [npm](https://docs.gitlab.com/ee/user/packages/npm_registry/index.html) *11.7+* | Host your own node.js packages. |
| [NuGet](https://gitlab.com/gitlab-org/gitlab/issues/20050) *Planned for 12.7*| Host NuGet packages in GitLab, and pull libraries into your various Visual Studio .NET applications. |
| [Opkg](https://gitlab.com/gitlab-org/gitlab/issues/36894) | Optimize your work with OpenWrt using Opkg repositories. |
| [P2](https://gitlab.com/gitlab-org/gitlab/issues/36895) | Host all your Eclipse plugins in your own GitLab P2 repository. |
| [PHP Composer](https://gitlab.com/gitlab-org/gitlab/issues/15886) | Provision Composer packages from GitLab and access Packagist and other remote Composer metadata repositories. |
| [Puppet](https://gitlab.com/gitlab-org/gitlab/issues/36897) | Configuration management meets repository management with Puppet repositories. |
| [PyPi](https://gitlab.com/gitlab-org/gitlab/issues/10483) | Host PyPi distributions. |
| [RPM](https://gitlab.com/gitlab-org/gitlab/issues/5932) | Distribute RPMs directly from GitLab. |
| [RubyGems](https://gitlab.com/gitlab-org/gitlab/issues/803) | Use GitLab to host your own gems. |
| [SBT](https://gitlab.com/gitlab-org/gitlab/issues/36898) | Resolve dependencies from and deploy build output to SBT repositories when running SBT builds. |
| [Vagrant](https://gitlab.com/gitlab-org/gitlab/issues/36899) | Securely host your Vagrant boxes in local repositories. |
## General information
The existing database model requires the following:
......
......@@ -127,23 +127,23 @@ dependency_scanning:
Dependency Scanning can be [configured](#customizing-the-dependency-scanning-settings)
using environment variables.
| Environment variable | Description | Example usage |
| --------------------------------------- | ----------- | ------------- |
| `DS_ANALYZER_IMAGES` | Comma separated list of custom images. The official default images are still enabled. Read more about [customizing analyzers](analyzers.md). | |
| `DS_ANALYZER_IMAGE_PREFIX` | Override the name of the Docker registry providing the official default images (proxy). Read more about [customizing analyzers](analyzers.md). | |
| `DS_ANALYZER_IMAGE_TAG` | Override the Docker tag of the official default images. Read more about [customizing analyzers](analyzers.md). | |
| `DS_PYTHON_VERSION` | Version of Python. If set to 2, dependencies are installed using Python 2.7 instead of Python 3.6. ([Introduced](https://gitlab.com/gitlab-org/gitlab/issues/12296) in GitLab 12.1)| |
| `DS_PIP_DEPENDENCY_PATH` | Path to load Python pip dependencies from. ([Introduced](https://gitlab.com/gitlab-org/gitlab/issues/12412) in GitLab 12.2) | |
| `DS_DEFAULT_ANALYZERS` | Override the names of the official default images. Read more about [customizing analyzers](analyzers.md). | |
| `DS_DISABLE_DIND` | Disable Docker in Docker and run analyzers [individually](#disabling-docker-in-docker-for-dependency-scanning).| |
| `DS_PULL_ANALYZER_IMAGES` | Pull the images from the Docker registry (set to `0` to disable). | |
| `DS_EXCLUDED_PATHS` | Exclude vulnerabilities from output based on the paths. A comma-separated list of patterns. Patterns can be globs, file or folder paths. Parent directories will also match patterns. | `DS_EXCLUDED_PATHS=doc,spec` |
| `DS_DOCKER_CLIENT_NEGOTIATION_TIMEOUT` | Time limit for Docker client negotiation. Timeouts are parsed using Go's [`ParseDuration`](https://golang.org/pkg/time/#ParseDuration). Valid time units are `ns`, `us` (or `µs`), `ms`, `s`, `m`, `h`. For example, `300ms`, `1.5h`, or `2h45m`. | |
| `DS_PULL_ANALYZER_IMAGE_TIMEOUT` | Time limit when pulling the image of an analyzer. Timeouts are parsed using Go's [`ParseDuration`](https://golang.org/pkg/time/#ParseDuration). Valid time units are `ns`, `us` (or `µs`), `ms`, `s`, `m`, `h`. For example, `300ms`, `1.5h`, or `2h45m`. | |
| `DS_RUN_ANALYZER_TIMEOUT` | Time limit when running an analyzer. Timeouts are parsed using Go's [`ParseDuration`](https://golang.org/pkg/time/#ParseDuration). Valid time units are `ns`, `us` (or `µs`), `ms`, `s`, `m`, `h`. For example, `300ms`, `1.5h`, or `2h45m`. | |
| `PIP_INDEX_URL` | Base URL of Python Package Index (default `https://pypi.org/simple`). | |
| `PIP_EXTRA_INDEX_URL` | Array of [extra URLs](https://pip.pypa.io/en/stable/reference/pip_install/#cmdoption-extra-index-url) of package indexes to use in addition to `PIP_INDEX_URL`. Comma separated. | |
| `MAVEN_CLI_OPTS` | List of command line arguments that will be passed to the maven analyzer during the project's build phase (see example for [using private repos](#using-private-maven-repos)). | |
| Environment variable | Description |
| --------------------------------------- | ----------- |
| `DS_ANALYZER_IMAGES` | Comma separated list of custom images. The official default images are still enabled. Read more about [customizing analyzers](analyzers.md). |
| `DS_ANALYZER_IMAGE_PREFIX` | Override the name of the Docker registry providing the official default images (proxy). Read more about [customizing analyzers](analyzers.md). |
| `DS_ANALYZER_IMAGE_TAG` | Override the Docker tag of the official default images. Read more about [customizing analyzers](analyzers.md). |
| `DS_PYTHON_VERSION` | Version of Python. If set to 2, dependencies are installed using Python 2.7 instead of Python 3.6. ([Introduced](https://gitlab.com/gitlab-org/gitlab/issues/12296) in GitLab 12.1)|
| `DS_PIP_DEPENDENCY_PATH` | Path to load Python pip dependencies from. ([Introduced](https://gitlab.com/gitlab-org/gitlab/issues/12412) in GitLab 12.2) |
| `DS_DEFAULT_ANALYZERS` | Override the names of the official default images. Read more about [customizing analyzers](analyzers.md). |
| `DS_DISABLE_DIND` | Disable Docker in Docker and run analyzers [individually](#disabling-docker-in-docker-for-dependency-scanning).|
| `DS_PULL_ANALYZER_IMAGES` | Pull the images from the Docker registry (set to `0` to disable). |
| `DS_EXCLUDED_PATHS` | Exclude vulnerabilities from output based on the paths. A comma-separated list of patterns. Patterns can be globs, file or folder paths (e.g., `doc,spec`). Parent directories will also match patterns. |
| `DS_DOCKER_CLIENT_NEGOTIATION_TIMEOUT` | Time limit for Docker client negotiation. Timeouts are parsed using Go's [`ParseDuration`](https://golang.org/pkg/time/#ParseDuration). Valid time units are `ns`, `us` (or `µs`), `ms`, `s`, `m`, `h`. For example, `300ms`, `1.5h`, or `2h45m`. |
| `DS_PULL_ANALYZER_IMAGE_TIMEOUT` | Time limit when pulling the image of an analyzer. Timeouts are parsed using Go's [`ParseDuration`](https://golang.org/pkg/time/#ParseDuration). Valid time units are `ns`, `us` (or `µs`), `ms`, `s`, `m`, `h`. For example, `300ms`, `1.5h`, or `2h45m`. |
| `DS_RUN_ANALYZER_TIMEOUT` | Time limit when running an analyzer. Timeouts are parsed using Go's [`ParseDuration`](https://golang.org/pkg/time/#ParseDuration). Valid time units are `ns`, `us` (or `µs`), `ms`, `s`, `m`, `h`. For example, `300ms`, `1.5h`, or `2h45m`. |
| `PIP_INDEX_URL` | Base URL of Python Package Index (default `https://pypi.org/simple`). |
| `PIP_EXTRA_INDEX_URL` | Array of [extra URLs](https://pip.pypa.io/en/stable/reference/pip_install/#cmdoption-extra-index-url) of package indexes to use in addition to `PIP_INDEX_URL`. Comma separated. |
| `MAVEN_CLI_OPTS` | List of command line arguments that will be passed to the maven analyzer during the project's build phase (see example for [using private repos](#using-private-maven-repos)). |
### Using private Maven repos
......
......@@ -206,14 +206,14 @@ The following are Docker image-related variables.
Some analyzers make it possible to filter out vulnerabilities under a given threshold.
| Environment variable | Default value | Description | Example usage |
|----------------------|---------------|-------------|---|
| `SAST_BANDIT_EXCLUDED_PATHS` | - | comma-separated list of paths to exclude from scan. Uses Python's [`fnmatch` syntax](https://docs.python.org/2/library/fnmatch.html) | |
| `SAST_BRAKEMAN_LEVEL` | 1 | Ignore Brakeman vulnerabilities under given confidence level. Integer, 1=Low 3=High. | |
| `SAST_FLAWFINDER_LEVEL` | 1 | Ignore Flawfinder vulnerabilities under given risk level. Integer, 0=No risk, 5=High risk. | |
| `SAST_GITLEAKS_ENTROPY_LEVEL` | 8.0 | Minimum entropy for secret detection. Float, 0.0 = low, 8.0 = high. | |
| `SAST_GOSEC_LEVEL` | 0 | Ignore gosec vulnerabilities under given confidence level. Integer, 0=Undefined, 1=Low, 2=Medium, 3=High. | |
| `SAST_EXCLUDED_PATHS` | - | Exclude vulnerabilities from output based on the paths. This is a comma-separated list of patterns. Patterns can be globs, file or folder paths. Parent directories will also match patterns. | `SAST_EXCLUDED_PATHS=doc,spec` |
| Environment variable | Default value | Description |
|----------------------|---------------|-------------|
| `SAST_BANDIT_EXCLUDED_PATHS` | - | comma-separated list of paths to exclude from scan. Uses Python's [`fnmatch` syntax](https://docs.python.org/2/library/fnmatch.html) |
| `SAST_BRAKEMAN_LEVEL` | 1 | Ignore Brakeman vulnerabilities under given confidence level. Integer, 1=Low 3=High. |
| `SAST_FLAWFINDER_LEVEL` | 1 | Ignore Flawfinder vulnerabilities under given risk level. Integer, 0=No risk, 5=High risk. |
| `SAST_GITLEAKS_ENTROPY_LEVEL` | 8.0 | Minimum entropy for secret detection. Float, 0.0 = low, 8.0 = high. |
| `SAST_GOSEC_LEVEL` | 0 | Ignore gosec vulnerabilities under given confidence level. Integer, 0=Undefined, 1=Low, 2=Medium, 3=High. |
| `SAST_EXCLUDED_PATHS` | - | Exclude vulnerabilities from output based on the paths. This is a comma-separated list of patterns. Patterns can be globs, file or folder paths (e.g., `doc,spec` ). Parent directories will also match patterns. |
#### Timeouts
......
......@@ -13,8 +13,12 @@ The Packages feature allows GitLab to act as a repository for the following:
| [Conan Repository](conan_repository/index.md) **(PREMIUM)** | The GitLab Conan Repository enables every project in GitLab to have its own space to store [Conan](https://conan.io/) packages. | 12.4+ |
| [Maven Repository](maven_repository/index.md) **(PREMIUM)** | The GitLab Maven Repository enables every project in GitLab to have its own space to store [Maven](https://maven.apache.org/) packages. | 11.3+ |
| [NPM Registry](npm_registry/index.md) **(PREMIUM)** | The GitLab NPM Registry enables every project in GitLab to have its own space to store [NPM](https://www.npmjs.com/) packages. | 11.7+ |
| [NuGet Repository](https://gitlab.com/gitlab-org/gitlab/issues/20050) **(PREMIUM)** | *COMING SOON* The GitLab NuGet Repository will enable every project in GitLab to have its own space to store [NuGet](https://www.nuget.org/) packages. | 12.7 (planned) |
TIP: **Tip:**
Don't you see your package management system supported yet? Consider contributing
to GitLab. This [development documentation](../../development/packages.md) will
guide you through the process.
guide you through the process. Or check out how other members of the commmunity
are adding support for [PHP](https://gitlab.com/gitlab-org/gitlab/merge_requests/17417) or [Terraform](https://gitlab.com/gitlab-org/gitlab/merge_requests/18834).
NOTE: **Note** We are especially interested in adding support for [PyPi](https://gitlab.com/gitlab-org/gitlab/issues/10483), [RubyGems](https://gitlab.com/gitlab-org/gitlab/issues/803), [Debian](https://gitlab.com/gitlab-org/gitlab/issues/5835), and [RPM](https://gitlab.com/gitlab-org/gitlab/issues/5932).
......@@ -156,9 +156,10 @@ a given scope, you will receive a `403 Forbidden!` error.
## Uploading a package with the same version twice
If you upload a package with a same name and version twice, GitLab will show
both packages in the UI, but the GitLab NPM Registry will expose the most recent
one as it supports only one package per version for `npm install`.
You cannot upload a package with the same name and version twice, unless you
delete the existing package first. This aligns with npmjs.org's behavior, with
the exception that npmjs.org does not allow users to ever publish the same version
more than once, even if it has been deleted.
## Troubleshooting
......
......@@ -8,9 +8,9 @@ Quick actions are textual shortcuts for common actions on issues, epics, merge r
and commits that are usually done by clicking buttons or dropdowns in GitLab's UI.
You can enter these commands while creating a new issue or merge request, or
in comments of issues, epics, merge requests, and commits. Each command should be
on a separate line in order to be properly detected and executed. Once executed,
on a separate line in order to be properly detected and executed.
> From [GitLab 12.1](https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/26672), an alert is displayed when a quick action is successfully applied.
> From [GitLab 12.1](https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/26672), once an action is executed, an alert is displayed when a quick action is successfully applied.
## Quick Actions for issues, merge requests and epics
......
......@@ -6,7 +6,7 @@
variables:
DS_ANALYZER_IMAGE_PREFIX: "registry.gitlab.com/gitlab-org/security-products/analyzers"
DS_DEFAULT_ANALYZERS: "gemnasium, retire.js, gemnasium-python, gemnasium-maven, bundler-audit"
DS_DEFAULT_ANALYZERS: "bundler-audit, retire.js, gemnasium, gemnasium-maven, gemnasium-python"
DS_MAJOR_VERSION: 2
DS_DISABLE_DIND: "false"
......
......@@ -13,6 +13,16 @@ module Gitlab
%w(kubectl apply -f).concat([filename], args).shelljoin
end
def delete_crds_from_group(group)
api_resources_args = %w(-o name --api-group).push(group)
api_resources(*api_resources_args) + " | xargs " + delete('--ignore-not-found', 'crd')
end
def api_resources(*args)
%w(kubectl api-resources).concat(args).shelljoin
end
end
end
end
......
......@@ -5,6 +5,14 @@ module Sentry
Error = Class.new(StandardError)
MissingKeysError = Class.new(StandardError)
ResponseInvalidSizeError = Class.new(StandardError)
BadRequestError = Class.new(StandardError)
SENTRY_API_SORT_VALUE_MAP = {
# <accepted_by_client> => <accepted_by_sentry_api>
'frequency' => 'freq',
'first_seen' => 'new',
'last_seen' => nil
}.freeze
attr_accessor :url, :token
......@@ -25,12 +33,8 @@ module Sentry
map_to_event(latest_event)
end
def list_issues(issue_status:, limit:, search_term: '')
issues = get_issues(
issue_status: issue_status,
limit: limit,
search_term: search_term
)
def list_issues(**keyword_args)
issues = get_issues(keyword_args)
validate_size(issues)
......@@ -52,14 +56,14 @@ module Sentry
def validate_size(issues)
return if Gitlab::Utils::DeepSize.new(issues).valid?
raise Client::ResponseInvalidSizeError, "Sentry API response is too big. Limit is #{Gitlab::Utils::DeepSize.human_default_max_size}."
raise ResponseInvalidSizeError, "Sentry API response is too big. Limit is #{Gitlab::Utils::DeepSize.human_default_max_size}."
end
def handle_mapping_exceptions(&block)
yield
rescue KeyError => e
Gitlab::Sentry.track_acceptable_exception(e)
raise Client::MissingKeysError, "Sentry API response is missing keys. #{e.message}"
raise MissingKeysError, "Sentry API response is missing keys. #{e.message}"
end
def request_params
......@@ -78,13 +82,25 @@ module Sentry
handle_response(response)
end
def get_issues(issue_status:, limit:, search_term: '')
query = "is:#{issue_status} #{search_term}".strip
def get_issues(**keyword_args)
http_get(
issues_api_url,
query: list_issue_sentry_query(keyword_args)
)
end
def list_issue_sentry_query(issue_status:, limit:, sort: nil, search_term: '')
unless SENTRY_API_SORT_VALUE_MAP.key?(sort)
raise BadRequestError, 'Invalid value for sort param'
end
query_params = {
query: "is:#{issue_status} #{search_term}".strip,
limit: limit,
sort: SENTRY_API_SORT_VALUE_MAP[sort]
}
http_get(issues_api_url, query: {
query: query,
limit: limit
})
query_params.compact
end
def get_issue(issue_id:)
......
......@@ -5692,9 +5692,6 @@ msgstr ""
msgid "Deployed"
msgstr ""
msgid "Deployed %{deployedSince}"
msgstr ""
msgid "Deployed to"
msgstr ""
......@@ -10390,6 +10387,9 @@ msgstr ""
msgid "Logs|To see the pod logs, deploy your code to an environment."
msgstr ""
msgid "MB"
msgstr ""
msgid "MD5"
msgstr ""
......
......@@ -6,16 +6,16 @@ module QA
attr_accessor :name, :expires_at
attribute :username do
Page::Project::Settings::Repository.perform do |page| # rubocop:disable QA/AmbiguousPageObjectName
page.expand_deploy_tokens do |token|
Page::Project::Settings::Repository.perform do |repository_page|
repository_page.expand_deploy_tokens do |token|
token.token_username
end
end
end
attribute :password do
Page::Project::Settings::Repository.perform do |page| # rubocop:disable QA/AmbiguousPageObjectName
page.expand_deploy_tokens do |token|
Page::Project::Settings::Repository.perform do |repository_page|
repository_page.expand_deploy_tokens do |token|
token.token_password
end
end
......
......@@ -27,11 +27,11 @@ module QA
Page::Project::Show.perform(&:create_first_new_file!)
Page::File::Form.perform do |page| # rubocop:disable QA/AmbiguousPageObjectName
page.add_name(@name)
page.add_content(@content)
page.add_commit_message(@commit_message)
page.commit_changes
Page::File::Form.perform do |form|
form.add_name(@name)
form.add_content(@content)
form.add_commit_message(@commit_message)
form.commit_changes
end
end
......
......@@ -41,8 +41,8 @@ module QA
fork_new.choose_namespace(user.name)
end
Page::Layout::Banner.perform do |page| # rubocop:disable QA/AmbiguousPageObjectName
page.has_notice?('The project was successfully forked.')
Page::Layout::Banner.perform do |banner|
banner.has_notice?('The project was successfully forked.')
end
populate(:project)
......
......@@ -24,36 +24,36 @@ module QA
Page::Project::Operations::Kubernetes::Add.perform(
&:add_existing_cluster)
Page::Project::Operations::Kubernetes::AddExisting.perform do |page| # rubocop:disable QA/AmbiguousPageObjectName
page.set_cluster_name(@cluster.cluster_name)
page.set_api_url(@cluster.api_url)
page.set_ca_certificate(@cluster.ca_certificate)
page.set_token(@cluster.token)
page.uncheck_rbac! unless @cluster.rbac
page.add_cluster!
Page::Project::Operations::Kubernetes::AddExisting.perform do |cluster_page|
cluster_page.set_cluster_name(@cluster.cluster_name)
cluster_page.set_api_url(@cluster.api_url)
cluster_page.set_ca_certificate(@cluster.ca_certificate)
cluster_page.set_token(@cluster.token)
cluster_page.uncheck_rbac! unless @cluster.rbac
cluster_page.add_cluster!
end
if @install_helm_tiller
Page::Project::Operations::Kubernetes::Show.perform do |page| # rubocop:disable QA/AmbiguousPageObjectName
Page::Project::Operations::Kubernetes::Show.perform do |show|
# We must wait a few seconds for permissions to be set up correctly for new cluster
sleep 10
# Helm must be installed before everything else
page.install!(:helm)
page.await_installed(:helm)
show.install!(:helm)
show.await_installed(:helm)
page.install!(:ingress) if @install_ingress
page.install!(:prometheus) if @install_prometheus
page.install!(:runner) if @install_runner
show.install!(:ingress) if @install_ingress
show.install!(:prometheus) if @install_prometheus
show.install!(:runner) if @install_runner
page.await_installed(:ingress) if @install_ingress
page.await_installed(:prometheus) if @install_prometheus
page.await_installed(:runner) if @install_runner
show.await_installed(:ingress) if @install_ingress
show.await_installed(:prometheus) if @install_prometheus
show.await_installed(:runner) if @install_runner
if @install_ingress
populate(:ingress_ip)
page.set_domain("#{ingress_ip}.nip.io")
page.save_domain
show.set_domain("#{ingress_ip}.nip.io")
show.save_domain
end
end
end
......
......@@ -28,11 +28,11 @@ module QA
Page::Project::Menu.perform(&:go_to_labels)
Page::Label::Index.perform(&:click_new_label_button)
Page::Label::New.perform do |page| # rubocop:disable QA/AmbiguousPageObjectName
page.fill_title(@title)
page.fill_description(@description)
page.fill_color(@color)
page.click_label_create_button
Page::Label::New.perform do |new_page|
new_page.fill_title(@title)
new_page.fill_description(@description)
new_page.fill_color(@color)
new_page.click_label_create_button
end
end
......
......@@ -65,17 +65,17 @@ module QA
project.visit!
Page::Project::Show.perform(&:new_merge_request)
Page::MergeRequest::New.perform do |new|
new.fill_title(@title)
new.fill_description(@description)
new.choose_milestone(@milestone) if @milestone
new.assign_to_me if @assignee == 'me'
Page::MergeRequest::New.perform do |new_page|
new_page.fill_title(@title)
new_page.fill_description(@description)
new_page.choose_milestone(@milestone) if @milestone
new_page.assign_to_me if @assignee == 'me'
labels.each do |label|
new.select_label(label)
new_page.select_label(label)
end
new.add_approval_rules(approval_rules) if approval_rules
new_page.add_approval_rules(approval_rules) if approval_rules
new.create_merge_request
new_page.create_merge_request
end
end
......
......@@ -16,10 +16,10 @@ module QA
Page::Main::Menu.perform(&:click_settings_link)
Page::Profile::Menu.perform(&:click_access_tokens)
Page::Profile::PersonalAccessTokens.perform do |page| # rubocop:disable QA/AmbiguousPageObjectName
page.fill_token_name(name || 'api-test-token')
page.check_api
page.click_create_token_button
Page::Profile::PersonalAccessTokens.perform do |token_page|
token_page.fill_token_name(name || 'api-test-token')
token_page.check_api
token_page.click_create_token_button
end
end
end
......
......@@ -32,14 +32,14 @@ module QA
end
attribute :repository_ssh_location do
Page::Project::Show.perform do |page| # rubocop:disable QA/AmbiguousPageObjectName
page.repository_clone_ssh_location
Page::Project::Show.perform do |show|
show.repository_clone_ssh_location
end
end
attribute :repository_http_location do
Page::Project::Show.perform do |page| # rubocop:disable QA/AmbiguousPageObjectName
page.repository_clone_http_location
Page::Project::Show.perform do |show|
show.repository_clone_http_location
end
end
......@@ -62,13 +62,13 @@ module QA
Page::Group::Show.perform(&:go_to_new_project)
end
Page::Project::New.perform do |page| # rubocop:disable QA/AmbiguousPageObjectName
page.choose_test_namespace
page.choose_name(@name)
page.add_description(@description)
page.set_visibility(@visibility)
page.enable_initialize_with_readme if @initialize_with_readme
page.create_new_project
Page::Project::New.perform do |new_page|
new_page.choose_test_namespace
new_page.choose_name(@name)
new_page.add_description(@description)
new_page.set_visibility(@visibility)
new_page.enable_initialize_with_readme if @initialize_with_readme
new_page.create_new_project
end
end
......
......@@ -17,18 +17,14 @@ module QA
Page::Group::Show.perform(&:go_to_new_project)
Page::Project::New.perform do |page| # rubocop:disable QA/AmbiguousPageObjectName
page.click_import_project
end
Page::Project::New.perform(&:click_import_project)
Page::Project::New.perform do |page| # rubocop:disable QA/AmbiguousPageObjectName
page.click_github_link
end
Page::Project::New.perform(&:click_github_link)
Page::Project::Import::Github.perform do |page| # rubocop:disable QA/AmbiguousPageObjectName
page.add_personal_access_token(@personal_access_token)
page.list_repos
page.import!(@github_repository_path, @name)
Page::Project::Import::Github.perform do |import_page|
import_page.add_personal_access_token(@personal_access_token)
import_page.list_repos
import_page.import!(@github_repository_path, @name)
end
end
end
......
......@@ -18,9 +18,9 @@ module QA
def fabricate!
project.visit!
Page::Project::Menu.perform do |page| # rubocop:disable QA/AmbiguousPageObjectName
page.click_issues
page.click_milestones
Page::Project::Menu.perform do |menu|
menu.click_issues
menu.click_milestones
end
Page::Project::Milestone::Index.perform(&:click_new_milestone)
......
......@@ -16,13 +16,13 @@ module QA
def fabricate!
Page::Dashboard::Snippet::Index.perform(&:go_to_new_snippet_page)
Page::Dashboard::Snippet::New.perform do |page| # rubocop:disable QA/AmbiguousPageObjectName
page.fill_title(@title)
page.fill_description(@description)
page.set_visibility(@visibility)
page.fill_file_name(@file_name)
page.fill_file_content(@file_content)
page.click_create_snippet_button
Page::Dashboard::Snippet::New.perform do |new_page|
new_page.fill_title(@title)
new_page.fill_description(@description)
new_page.set_visibility(@visibility)
new_page.fill_file_name(@file_name)
new_page.fill_file_content(@file_content)
new_page.click_create_snippet_button
end
end
end
......
......@@ -17,8 +17,8 @@ module QA
Page::Main::Menu.perform(&:click_settings_link)
Page::Profile::Menu.perform(&:click_ssh_keys)
Page::Profile::SSHKeys.perform do |page| # rubocop:disable QA/AmbiguousPageObjectName
page.add_key(public_key, title)
Page::Profile::SSHKeys.perform do |profile_page|
profile_page.add_key(public_key, title)
end
end
end
......
......@@ -8,10 +8,10 @@ module QA
Page::Main::Login.perform(&:sign_in_using_credentials)
Page::Main::Menu.perform(&:go_to_groups)
Page::Dashboard::Groups.perform do |page| # rubocop:disable QA/AmbiguousPageObjectName
page.click_new_group
Page::Dashboard::Groups.perform do |groups|
groups.click_new_group
expect(page).to have_content(
expect(groups).to have_content(
/Create a Mattermost team for this group/
)
end
......
......@@ -11,8 +11,8 @@ module QA
Runtime::Browser.visit(:mattermost, Page::Mattermost::Login)
Page::Mattermost::Login.perform(&:sign_in_using_oauth)
Page::Mattermost::Main.perform do |page| # rubocop:disable QA/AmbiguousPageObjectName
expect(page).to have_content(/(Welcome to: Mattermost|Logout GitLab Mattermost)/)
Page::Mattermost::Main.perform do |mattermost|
expect(mattermost).to have_content(/(Welcome to: Mattermost|Logout GitLab Mattermost)/)
end
end
end
......
......@@ -15,8 +15,8 @@ module QA
project.visit!
Page::Project::Menu.perform(&:go_to_members_settings)
Page::Project::Settings::Members.perform do |page| # rubocop:disable QA/AmbiguousPageObjectName
page.add_member(user.username)
Page::Project::Settings::Members.perform do |members|
members.add_member(user.username)
end
expect(page).to have_content(/@#{user.username}(\n| )?Given access/)
......
......@@ -27,8 +27,8 @@ module QA
project.visit!
Page::Project::Show.perform(&:go_to_members_settings)
Page::Project::Settings::Members.perform do |page| # rubocop:disable QA/AmbiguousPageObjectName
page.add_member(user.username)
Page::Project::Settings::Members.perform do |members|
members.add_member(user.username)
end
issue = Resource::Issue.fabricate_via_api! do |issue|
......
......@@ -23,12 +23,12 @@ module QA
it 'user sees issue suggestions when creating a new issue' do
Page::Project::Show.perform(&:go_to_new_issue)
Page::Project::Issue::New.perform do |new|
new.add_title("issue")
expect(new).to have_content(issue_title)
Page::Project::Issue::New.perform do |new_page|
new_page.add_title("issue")
expect(new_page).to have_content(issue_title)
new.add_title("Issue Board")
expect(new).not_to have_content(issue_title)
new_page.add_title("Issue Board")
expect(new_page).not_to have_content(issue_title)
end
end
end
......
......@@ -50,10 +50,10 @@ module QA
@project.visit!
Page::Project::Show.perform(&:open_web_ide!)
Page::Project::WebIDE::Edit.perform do |page| # rubocop:disable QA/AmbiguousPageObjectName
page.create_new_file_from_template template[:file_name], template[:name]
Page::Project::WebIDE::Edit.perform do |ide|
ide.create_new_file_from_template template[:file_name], template[:name]
expect(page.has_file?(template[:file_name])).to be_truthy
expect(ide.has_file?(template[:file_name])).to be_truthy
end
expect(page).to have_button('Undo')
......
......@@ -16,9 +16,9 @@ module QA
validate_content('My First Wiki Content')
Page::Project::Wiki::Edit.perform(&:click_edit)
Page::Project::Wiki::New.perform do |page| # rubocop:disable QA/AmbiguousPageObjectName
page.set_content("My Second Wiki Content")
page.save_changes
Page::Project::Wiki::New.perform do |wiki|
wiki.set_content("My Second Wiki Content")
wiki.save_changes
end
validate_content('My Second Wiki Content')
......
......@@ -23,10 +23,10 @@ module QA
issue.title = 'Performance bar test'
end
Page::Layout::PerformanceBar.perform do |page| # rubocop:disable QA/AmbiguousPageObjectName
expect(page).to have_performance_bar
expect(page).to have_detailed_metrics
expect(page).to have_request_for('realtime_changes') # Always requested on issue pages
Page::Layout::PerformanceBar.perform do |bar_component|
expect(bar_component).to have_performance_bar
expect(bar_component).to have_detailed_metrics
expect(bar_component).to have_request_for('realtime_changes') # Always requested on issue pages
end
end
end
......
......@@ -75,8 +75,8 @@ class AutomatedCleanup
deployed_at = Time.parse(last_deploy)
if deployed_at < delete_threshold
environment = delete_environment(environment, deployment)
if environment
deleted_environment = delete_environment(environment, deployment)
if deleted_environment
release = Quality::HelmClient::Release.new(environment.slug, 1, deployed_at.to_s, nil, nil, review_apps_namespace)
releases_to_delete << release
end
......
......@@ -48,20 +48,17 @@ describe Projects::ErrorTrackingController do
describe 'format json' do
let(:list_issues_service) { spy(:list_issues_service) }
let(:external_url) { 'http://example.com' }
let(:search_term) do
ActionController::Parameters.new(
search_term: 'something'
).permit!
end
context 'no data' do
let(:search_term) do
let(:params) { project_params(format: :json) }
let(:permitted_params) do
ActionController::Parameters.new({}).permit!
end
before do
expect(ErrorTracking::ListIssuesService)
.to receive(:new).with(project, user, search_term)
.to receive(:new).with(project, user, permitted_params)
.and_return(list_issues_service)
expect(list_issues_service).to receive(:execute)
......@@ -75,10 +72,16 @@ describe Projects::ErrorTrackingController do
end
end
context 'with a search_term param' do
context 'with a search_term and sort params' do
let(:params) { project_params(format: :json, search_term: 'something', sort: 'last_seen') }
let(:permitted_params) do
ActionController::Parameters.new(search_term: 'something', sort: 'last_seen').permit!
end
before do
expect(ErrorTracking::ListIssuesService)
.to receive(:new).with(project, user, search_term)
.to receive(:new).with(project, user, permitted_params)
.and_return(list_issues_service)
end
......@@ -93,7 +96,7 @@ describe Projects::ErrorTrackingController do
let(:error) { build(:error_tracking_error) }
it 'returns a list of errors' do
get :index, params: project_params(format: :json, search_term: 'something')
get :index, params: params
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('error_tracking/index')
......@@ -103,7 +106,7 @@ describe Projects::ErrorTrackingController do
end
end
context 'without a search_term param' do
context 'without params' do
before do
expect(ErrorTracking::ListIssuesService)
.to receive(:new).with(project, user, {})
......
......@@ -13,6 +13,8 @@ describe('Rollback Component', () => {
isLastDeployment: true,
environment: {},
},
attachToDocument: true,
sync: false,
});
expect(wrapper.element).toHaveSpriteIcon('repeat');
......@@ -25,6 +27,8 @@ describe('Rollback Component', () => {
isLastDeployment: false,
environment: {},
},
attachToDocument: true,
sync: false,
});
expect(wrapper.element).toHaveSpriteIcon('redo');
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`MemoryGraph Render chart should draw container with chart 1`] = `
<div
class="memory-graph-container p-1"
style="width: 100px;"
>
<glsparklinechart-stub
data="Nov 12 2019 19:17:33,2.87,Nov 12 2019 19:18:33,2.78,Nov 12 2019 19:19:33,2.78,Nov 12 2019 19:20:33,3.01"
height="25"
tooltiplabel="MB"
variant="gray900"
/>
</div>
`;
import Vue from 'vue';
import { shallowMount } from '@vue/test-utils';
import MemoryGraph from '~/vue_shared/components/memory_graph.vue';
import { GlSparklineChart } from '@gitlab/ui/dist/charts';
describe('MemoryGraph', () => {
const Component = Vue.extend(MemoryGraph);
let wrapper;
const metrics = [
[1573586253.853, '2.87'],
[1573586313.853, '2.77734375'],
[1573586373.853, '2.77734375'],
[1573586433.853, '3.0066964285714284'],
];
afterEach(() => {
wrapper.destroy();
});
beforeEach(() => {
wrapper = shallowMount(Component, {
propsData: {
metrics,
width: 100,
height: 25,
},
});
});
describe('chartData', () => {
it('should calculate chartData', () => {
expect(wrapper.vm.chartData.length).toEqual(metrics.length);
});
it('should format date & MB values', () => {
const formattedData = [
['Nov 12 2019 19:17:33', '2.87'],
['Nov 12 2019 19:18:33', '2.78'],
['Nov 12 2019 19:19:33', '2.78'],
['Nov 12 2019 19:20:33', '3.01'],
];
expect(wrapper.vm.chartData).toEqual(formattedData);
});
});
describe('Render chart', () => {
it('should draw container with chart', () => {
expect(wrapper.element).toMatchSnapshot();
expect(wrapper.find('.memory-graph-container').exists()).toBe(true);
expect(wrapper.find(GlSparklineChart).exists()).toBe(true);
});
});
});
# frozen_string_literal: true
require 'spec_helper'
describe Mutations::Todos::Restore do
let_it_be(:current_user) { create(:user) }
let_it_be(:author) { create(:user) }
let_it_be(:other_user) { create(:user) }
let_it_be(:todo1) { create(:todo, user: current_user, author: author, state: :done) }
let_it_be(:todo2) { create(:todo, user: current_user, author: author, state: :pending) }
let_it_be(:other_user_todo) { create(:todo, user: other_user, author: author, state: :done) }
let(:mutation) { described_class.new(object: nil, context: { current_user: current_user }) }
describe '#resolve' do
it 'restores a single todo' do
result = restore_mutation(todo1)
expect(todo1.reload.state).to eq('pending')
expect(todo2.reload.state).to eq('pending')
expect(other_user_todo.reload.state).to eq('done')
todo = result[:todo]
expect(todo.id).to eq(todo1.id)
expect(todo.state).to eq('pending')
end
it 'handles a todo which is already pending as expected' do
result = restore_mutation(todo2)
expect(todo1.reload.state).to eq('done')
expect(todo2.reload.state).to eq('pending')
expect(other_user_todo.reload.state).to eq('done')
todo = result[:todo]
expect(todo.id).to eq(todo2.id)
expect(todo.state).to eq('pending')
end
it 'ignores requests for todos which do not belong to the current user' do
expect { restore_mutation(other_user_todo) }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
expect(todo1.reload.state).to eq('done')
expect(todo2.reload.state).to eq('pending')
expect(other_user_todo.reload.state).to eq('done')
end
it 'ignores invalid GIDs' do
expect { mutation.resolve(id: 'invalid_gid') }.to raise_error(Gitlab::Graphql::Errors::ArgumentError)
expect(todo1.reload.state).to eq('done')
expect(todo2.reload.state).to eq('pending')
expect(other_user_todo.reload.state).to eq('done')
end
end
def restore_mutation(todo)
mutation.resolve(id: global_id_of(todo))
end
def global_id_of(todo)
todo.to_global_id.to_s
end
end
......@@ -185,6 +185,7 @@ describe('MemoryUsage', () => {
vm.loadingMetrics = false;
vm.hasMetrics = true;
vm.loadFailed = false;
vm.memoryMetrics = metricsMockData.metrics.memory_values[0].values;
Vue.nextTick(() => {
expect(el.querySelector('.memory-graph-container')).toBeDefined();
......
import Vue from 'vue';
import MemoryGraph from '~/vue_shared/components/memory_graph.vue';
import { mockMetrics, mockMedian, mockMedianIndex } from './mock_data';
const defaultHeight = '25';
const defaultWidth = '100';
const createComponent = () => {
const Component = Vue.extend(MemoryGraph);
return new Component({
el: document.createElement('div'),
propsData: {
metrics: [],
deploymentTime: 0,
width: '',
height: '',
pathD: '',
pathViewBox: '',
dotX: '',
dotY: '',
},
});
};
describe('MemoryGraph', () => {
let vm;
let el;
beforeEach(() => {
vm = createComponent();
el = vm.$el;
});
describe('data', () => {
it('should have default data', () => {
const data = MemoryGraph.data();
const dataValidator = (dataItem, expectedType, defaultVal) => {
expect(typeof dataItem).toBe(expectedType);
expect(dataItem).toBe(defaultVal);
};
dataValidator(data.pathD, 'string', '');
dataValidator(data.pathViewBox, 'string', '');
dataValidator(data.dotX, 'string', '');
dataValidator(data.dotY, 'string', '');
});
});
describe('computed', () => {
describe('getFormattedMedian', () => {
it('should show human readable median value based on provided median timestamp', () => {
vm.deploymentTime = mockMedian;
const formattedMedian = vm.getFormattedMedian;
expect(formattedMedian.indexOf('Deployed')).toBeGreaterThan(-1);
expect(formattedMedian.indexOf('ago')).toBeGreaterThan(-1);
});
});
});
describe('methods', () => {
describe('getMedianMetricIndex', () => {
it('should return index of closest metric timestamp to that of median', () => {
const matchingIndex = vm.getMedianMetricIndex(mockMedian, mockMetrics);
expect(matchingIndex).toBe(mockMedianIndex);
});
});
describe('getGraphPlotValues', () => {
it('should return Object containing values to plot graph', () => {
const plotValues = vm.getGraphPlotValues(mockMedian, mockMetrics);
expect(plotValues.pathD).toBeDefined();
expect(Array.isArray(plotValues.pathD)).toBeTruthy();
expect(plotValues.pathViewBox).toBeDefined();
expect(typeof plotValues.pathViewBox).toBe('object');
expect(plotValues.dotX).toBeDefined();
expect(typeof plotValues.dotX).toBe('number');
expect(plotValues.dotY).toBeDefined();
expect(typeof plotValues.dotY).toBe('number');
});
});
});
describe('template', () => {
it('should render template elements correctly', () => {
expect(el.classList.contains('memory-graph-container')).toBeTruthy();
expect(el.querySelector('svg')).toBeDefined();
});
it('should render graph when renderGraph is called internally', done => {
const { pathD, pathViewBox, dotX, dotY } = vm.getGraphPlotValues(mockMedian, mockMetrics);
vm.height = defaultHeight;
vm.width = defaultWidth;
vm.pathD = `M ${pathD}`;
vm.pathViewBox = `0 0 ${pathViewBox.lineWidth} ${pathViewBox.diff}`;
vm.dotX = dotX;
vm.dotY = dotY;
Vue.nextTick(() => {
const svgEl = el.querySelector('svg');
expect(svgEl).toBeDefined();
expect(svgEl.getAttribute('height')).toBe(defaultHeight);
expect(svgEl.getAttribute('width')).toBe(defaultWidth);
const pathEl = el.querySelector('path');
expect(pathEl).toBeDefined();
expect(pathEl.getAttribute('d')).toBe(`M ${pathD}`);
expect(pathEl.getAttribute('viewBox')).toBe(
`0 0 ${pathViewBox.lineWidth} ${pathViewBox.diff}`,
);
const circleEl = el.querySelector('circle');
expect(circleEl).toBeDefined();
expect(circleEl.getAttribute('r')).toBe('1.5');
expect(circleEl.getAttribute('transform')).toBe('translate(0 -1)');
expect(circleEl.getAttribute('cx')).toBe(`${dotX}`);
expect(circleEl.getAttribute('cy')).toBe(`${dotY}`);
done();
});
});
});
});
export const mockMetrics = [
[1493716685, '4.30859375'],
[1493716745, '4.30859375'],
[1493716805, '4.30859375'],
[1493716865, '4.30859375'],
[1493716925, '4.30859375'],
[1493716985, '4.30859375'],
[1493717045, '4.30859375'],
[1493717105, '4.30859375'],
[1493717165, '4.30859375'],
[1493717225, '4.30859375'],
[1493717285, '4.30859375'],
[1493717345, '4.30859375'],
[1493717405, '4.30859375'],
[1493717465, '4.30859375'],
[1493717525, '4.30859375'],
[1493717585, '4.30859375'],
[1493717645, '4.30859375'],
[1493717705, '4.30859375'],
[1493717765, '4.30859375'],
[1493717825, '4.30859375'],
[1493717885, '4.30859375'],
[1493717945, '4.30859375'],
[1493718005, '4.30859375'],
[1493718065, '4.30859375'],
[1493718125, '4.30859375'],
[1493718185, '4.30859375'],
[1493718245, '4.30859375'],
[1493718305, '4.234375'],
[1493718365, '4.234375'],
[1493718425, '4.234375'],
[1493718485, '4.234375'],
[1493718545, '4.243489583333333'],
[1493718605, '4.2109375'],
[1493718665, '4.2109375'],
[1493718725, '4.2109375'],
[1493718785, '4.26171875'],
[1493718845, '4.26171875'],
[1493718905, '4.26171875'],
[1493718965, '4.26171875'],
[1493719025, '4.26171875'],
[1493719085, '4.26171875'],
[1493719145, '4.26171875'],
[1493719205, '4.26171875'],
[1493719265, '4.26171875'],
[1493719325, '4.26171875'],
[1493719385, '4.26171875'],
[1493719445, '4.26171875'],
[1493719505, '4.26171875'],
[1493719565, '4.26171875'],
[1493719625, '4.26171875'],
[1493719685, '4.26171875'],
[1493719745, '4.26171875'],
[1493719805, '4.26171875'],
[1493719865, '4.26171875'],
[1493719925, '4.26171875'],
[1493719985, '4.26171875'],
[1493720045, '4.26171875'],
[1493720105, '4.26171875'],
[1493720165, '4.26171875'],
[1493720225, '4.26171875'],
[1493720285, '4.26171875'],
];
export const mockMedian = 1493718485;
export const mockMedianIndex = 30;
......@@ -45,4 +45,20 @@ describe Gitlab::Kubernetes::KubectlCmd do
end
end
end
describe '.api_resources' do
it 'constructs string properly' do
expected_command = 'kubectl api-resources -o name --api-group foo'
expect(described_class.api_resources("-o", "name", "--api-group", "foo")).to eq expected_command
end
end
describe '.delete_crds_from_group' do
it 'constructs string properly' do
expected_command = 'kubectl api-resources -o name --api-group foo | xargs kubectl delete --ignore-not-found crd'
expect(described_class.delete_crds_from_group("foo")).to eq expected_command
end
end
end
......@@ -5,6 +5,12 @@ require 'spec_helper'
describe Sentry::Client do
let(:sentry_url) { 'https://sentrytest.gitlab.com/api/0/projects/sentry-org/sentry-project' }
let(:token) { 'test-token' }
let(:default_httparty_options) do
{
follow_redirects: false,
headers: { "Authorization" => "Bearer test-token" }
}
end
let(:issues_sample_response) do
Gitlab::Utils.deep_indifferent_access(
......@@ -94,7 +100,7 @@ describe Sentry::Client do
let!(:sentry_api_request) { stub_sentry_request(sentry_request_url, body: sentry_api_response) }
subject { client.list_issues(issue_status: issue_status, limit: limit, search_term: search_term) }
subject { client.list_issues(issue_status: issue_status, limit: limit, search_term: search_term, sort: 'last_seen') }
it_behaves_like 'calls sentry api'
......@@ -165,6 +171,35 @@ describe Sentry::Client do
end
end
context 'requests with sort parameter in sentry api' do
let(:sentry_request_url) do
'https://sentrytest.gitlab.com/api/0/projects/sentry-org/sentry-project/' \
'issues/?limit=20&query=is:unresolved&sort=freq'
end
let!(:sentry_api_request) { stub_sentry_request(sentry_request_url, body: sentry_api_response) }
subject { client.list_issues(issue_status: issue_status, limit: limit, sort: 'frequency') }
it 'calls the sentry api with sort params' do
expect(Gitlab::HTTP).to receive(:get).with(
URI("#{sentry_url}/issues/"),
default_httparty_options.merge(query: { limit: 20, query: "is:unresolved", sort: "freq" })
).and_call_original
subject
expect(sentry_api_request).to have_been_requested
end
end
context 'with invalid sort params' do
subject { client.list_issues(issue_status: issue_status, limit: limit, sort: 'fish') }
it 'throws an error' do
expect { subject }.to raise_error(Sentry::Client::BadRequestError, 'Invalid value for sort param')
end
end
context 'Older sentry versions where keys are not present' do
let(:sentry_api_response) do
issues_sample_response[0...1].map do |issue|
......
# frozen_string_literal: true
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20191015154408_drop_merge_requests_require_code_owner_approval_from_projects.rb')
describe DropMergeRequestsRequireCodeOwnerApprovalFromProjects, :migration do
let(:projects_table) { table(:projects) }
subject(:migration) { described_class.new }
describe "without running the migration" do
it "project_table has a :merge_requests_require_code_owner_approval column" do
expect(projects_table.column_names)
.to include("merge_requests_require_code_owner_approval")
end
it "project_table has a :projects_requiring_code_owner_approval index" do
expect(ActiveRecord::Base.connection.indexes(:projects).collect(&:name))
.to include("projects_requiring_code_owner_approval")
end
end
describe '#up' do
context "without running "
before do
migrate!
end
it "drops the :merge_requests_require_code_owner_approval column" do
expect(projects_table.column_names)
.not_to include("merge_requests_require_code_owner_approval")
end
it "drops the :projects_requiring_code_owner_approval index" do
expect(ActiveRecord::Base.connection.indexes(:projects).collect(&:name))
.not_to include("projects_requiring_code_owner_approval")
end
end
describe "#down" do
before do
migration.up
migration.down
end
it "project_table has a :merge_requests_require_code_owner_approval column" do
expect(projects_table.column_names)
.to include("merge_requests_require_code_owner_approval")
end
it "project_table has a :projects_requiring_code_owner_approval index" do
expect(ActiveRecord::Base.connection.indexes(:projects).collect(&:name))
.to include("projects_requiring_code_owner_approval")
end
end
end
......@@ -161,18 +161,19 @@ describe Clusters::Applications::Knative do
end
it "initializes command with all necessary postdelete script" do
api_resources = YAML.safe_load(File.read(Rails.root.join(Clusters::Applications::Knative::API_RESOURCES_PATH)))
api_groups = YAML.safe_load(File.read(Rails.root.join(Clusters::Applications::Knative::API_GROUPS_PATH)))
remove_knative_istio_leftovers_script = [
"kubectl delete --ignore-not-found ns knative-serving",
"kubectl delete --ignore-not-found ns knative-build"
]
full_delete_commands_size = api_resources.size + remove_knative_istio_leftovers_script.size
full_delete_commands_size = api_groups.size + remove_knative_istio_leftovers_script.size
expect(subject.postdelete).to include(*remove_knative_istio_leftovers_script)
expect(subject.postdelete.size).to eq(full_delete_commands_size)
expect(subject.postdelete[2]).to eq("kubectl delete --ignore-not-found crd #{api_resources[0]}")
expect(subject.postdelete[2]).to eq("kubectl api-resources -o name --api-group #{api_groups[0]} | xargs kubectl delete --ignore-not-found crd")
expect(subject.postdelete[3]).to eq("kubectl api-resources -o name --api-group #{api_groups[1]} | xargs kubectl delete --ignore-not-found crd")
end
end
......
# frozen_string_literal: true
require 'spec_helper'
describe 'Restoring Todos' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
let_it_be(:author) { create(:user) }
let_it_be(:other_user) { create(:user) }
let_it_be(:todo1) { create(:todo, user: current_user, author: author, state: :done) }
let_it_be(:todo2) { create(:todo, user: current_user, author: author, state: :pending) }
let_it_be(:other_user_todo) { create(:todo, user: other_user, author: author, state: :done) }
let(:input) { { id: todo1.to_global_id.to_s } }
let(:mutation) do
graphql_mutation(:todo_restore, input,
<<-QL.strip_heredoc
clientMutationId
errors
todo {
id
state
}
QL
)
end
def mutation_response
graphql_mutation_response(:todo_restore)
end
it 'restores a single todo' do
post_graphql_mutation(mutation, current_user: current_user)
expect(todo1.reload.state).to eq('pending')
expect(todo2.reload.state).to eq('pending')
expect(other_user_todo.reload.state).to eq('done')
todo = mutation_response['todo']
expect(todo['id']).to eq(todo1.to_global_id.to_s)
expect(todo['state']).to eq('pending')
end
context 'when todo is already marked pending' do
let(:input) { { id: todo2.to_global_id.to_s } }
it 'has the expected response' do
post_graphql_mutation(mutation, current_user: current_user)
expect(todo1.reload.state).to eq('done')
expect(todo2.reload.state).to eq('pending')
expect(other_user_todo.reload.state).to eq('done')
todo = mutation_response['todo']
expect(todo['id']).to eq(todo2.to_global_id.to_s)
expect(todo['state']).to eq('pending')
end
end
context 'when todo does not belong to requesting user' do
let(:input) { { id: other_user_todo.to_global_id.to_s } }
let(:access_error) { 'The resource that you are attempting to access does not exist or you don\'t have permission to perform this action' }
it 'contains the expected error' do
post_graphql_mutation(mutation, current_user: current_user)
errors = json_response['errors']
expect(errors).not_to be_blank
expect(errors.first['message']).to eq(access_error)
expect(todo1.reload.state).to eq('done')
expect(todo2.reload.state).to eq('pending')
expect(other_user_todo.reload.state).to eq('done')
end
end
context 'when using an invalid gid' do
let(:input) { { id: 'invalid_gid' } }
let(:invalid_gid_error) { 'invalid_gid is not a valid GitLab id.' }
it 'contains the expected error' do
post_graphql_mutation(mutation, current_user: current_user)
errors = json_response['errors']
expect(errors).not_to be_blank
expect(errors.first['message']).to eq(invalid_gid_error)
expect(todo1.reload.state).to eq('done')
expect(todo2.reload.state).to eq('pending')
expect(other_user_todo.reload.state).to eq('done')
end
end
end
......@@ -5,12 +5,13 @@ require 'spec_helper'
describe ErrorTracking::ListIssuesService do
set(:user) { create(:user) }
set(:project) { create(:project) }
let(:params) { { search_term: 'something' } }
let(:params) { { search_term: 'something', sort: 'last_seen' } }
let(:list_sentry_issues_args) do
{
issue_status: 'unresolved',
limit: 20,
search_term: params[:search_term]
search_term: params[:search_term],
sort: params[:sort]
}
end
......
# frozen_string_literal: true
require 'sidekiq/testing'
# If Sidekiq::Testing.inline! is used, SQL transactions done inside
# Sidekiq worker are included in the SQL query limit (in a real
# deployment sidekiq worker is executed separately). To avoid
# increasing SQL limit counter, the request is marked as whitelisted
# during Sidekiq block
class DisableQueryLimit
def call(worker_instance, msg, queue)
transaction = Gitlab::QueryLimiting::Transaction.current
if !transaction.respond_to?(:whitelisted) || transaction.whitelisted
yield
else
transaction.whitelisted = true
yield
transaction.whitelisted = false
end
end
end
Sidekiq::Testing.server_middleware do |chain|
chain.add Gitlab::SidekiqStatus::ServerMiddleware
chain.add DisableQueryLimit
end
RSpec.configure do |config|
config.around(:each, :sidekiq) do |example|
Sidekiq::Worker.clear_all
......
# frozen_string_literal: true
require 'sidekiq/testing'
# If Sidekiq::Testing.inline! is used, SQL transactions done inside
# Sidekiq worker are included in the SQL query limit (in a real
# deployment sidekiq worker is executed separately). To avoid
# increasing SQL limit counter, the request is marked as whitelisted
# during Sidekiq block
class DisableQueryLimit
def call(worker_instance, msg, queue)
transaction = Gitlab::QueryLimiting::Transaction.current
if !transaction.respond_to?(:whitelisted) || transaction.whitelisted
yield
else
transaction.whitelisted = true
yield
transaction.whitelisted = false
end
end
end
Sidekiq::Testing.server_middleware do |chain|
chain.add Gitlab::SidekiqStatus::ServerMiddleware
chain.add DisableQueryLimit
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment