Commit 180cd023 authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent be3e24ea
......@@ -142,6 +142,7 @@ dependency_scanning:
variables:
DOCKER_DRIVER: overlay2
DOCKER_TLS_CERTDIR: ""
DS_EXCLUDED_PATHS: "qa/qa/ee/fixtures/secure_premade_reports,spec,ee/spec"
allow_failure: true
services:
- docker:stable-dind
......
......@@ -44,6 +44,7 @@ const Api = {
releasePath: '/api/:version/projects/:id/releases/:tag_name',
mergeRequestsPipeline: '/api/:version/projects/:id/merge_requests/:merge_request_iid/pipelines',
adminStatisticsPath: '/api/:version/application/statistics',
pipelineSinglePath: '/api/:version/projects/:id/pipelines/:pipeline_id',
group(groupId, callback) {
const url = Api.buildUrl(Api.groupPath).replace(':id', groupId);
......@@ -448,6 +449,14 @@ const Api = {
return axios.get(url);
},
pipelineSingle(id, pipelineId) {
const url = Api.buildUrl(this.pipelineSinglePath)
.replace(':id', encodeURIComponent(id))
.replace(':pipeline_id', encodeURIComponent(pipelineId));
return axios.get(url);
},
buildUrl(url) {
return joinPaths(gon.relative_url_root || '', url.replace(':version', gon.api_version));
},
......
......@@ -17,6 +17,25 @@ module Types
group.avatar_url(only_path: false)
end
field :share_with_group_lock, GraphQL::BOOLEAN_TYPE, null: true,
description: 'Indicates if sharing a project with another group within this group is prevented'
field :project_creation_level, GraphQL::STRING_TYPE, null: true, method: :project_creation_level_str,
description: 'The permission level required to create projects in the group'
field :subgroup_creation_level, GraphQL::STRING_TYPE, null: true, method: :subgroup_creation_level_str,
description: 'The permission level required to create subgroups within the group'
field :require_two_factor_authentication, GraphQL::BOOLEAN_TYPE, null: true,
description: 'Indicates if all users in this group are required to set up two-factor authentication'
field :two_factor_grace_period, GraphQL::INT_TYPE, null: true,
description: 'Time before two-factor authentication is enforced'
field :auto_devops_enabled, GraphQL::BOOLEAN_TYPE, null: true,
description: 'Indicates whether Auto DevOps is enabled for all projects within this group'
field :emails_disabled, GraphQL::BOOLEAN_TYPE, null: true,
description: 'Indicates if a group has email notifications disabled'
field :mentions_disabled, GraphQL::BOOLEAN_TYPE, null: true,
description: 'Indicates if a group is disabled from getting mentioned'
......
......@@ -82,6 +82,7 @@ class Member < ApplicationRecord
scope :with_user, -> (user) { where(user: user) }
scope :with_source_id, ->(source_id) { where(source_id: source_id) }
scope :including_source, -> { includes(:source) }
scope :order_name_asc, -> { left_join_users.reorder(Gitlab::Database.nulls_last_order('users.name', 'ASC')) }
scope :order_name_desc, -> { left_join_users.reorder(Gitlab::Database.nulls_last_order('users.name', 'DESC')) }
......
......@@ -20,17 +20,11 @@ class ErrorTrackingIssueLinkWorker
def perform(issue_id)
@issue = Issue.find_by_id(issue_id)
return unless issue && error_tracking && sentry_issue_id
return unless valid?
try_obtain_lease do
logger.info("Linking Sentry issue #{sentry_issue_id} to GitLab issue #{issue.id}")
if integration_id.nil?
logger.info("Sentry integration unavailable for #{error_tracking.api_url}")
break
end
sentry_client.create_issue_link(integration_id, sentry_issue_id, issue)
rescue Sentry::Client::Error
logger.info("Failed to link Sentry issue #{sentry_issue_id} to GitLab issue #{issue.id}")
......@@ -39,6 +33,10 @@ class ErrorTrackingIssueLinkWorker
private
def valid?
issue && error_tracking && sentry_issue_id
end
def error_tracking
strong_memoize(:error_tracking) do
issue.project.error_tracking_setting
......
---
title: Packages published to the package registry via CI/CD with a CI_JOB_TOKEN will
display pipeline information on the details page
merge_request: 22485
author:
type: added
---
title: Add users memberships endpoints for admins
merge_request: 22518
author:
type: added
---
title: Allow setting minimum concurrency for sidekiq-cluster processes
merge_request: 23408
author:
type: added
---
title: Backfill missing GraphQL API Group type properties
merge_request: 23389
author: Fabio Huser
type: added
---
title: Upgrade to Gitaly v1.83.0
merge_request: 23431
author:
type: changed
---
title: Upgrade pages to 1.14.0
merge_request: 23317
author:
type: added
---
title: Sync GitLab issues with Sentry plugin integration
merge_request: 23355
author:
type: added
......@@ -124,9 +124,18 @@ number of threads that equals the number of queues, plus one spare thread.
For example, a process that handles the `process_commit` and `post_receive`
queues will use three threads in total.
## Limiting concurrency
## Managing concurrency
To limit the concurrency of the Sidekiq process:
When setting the maximum concurrency, keep in mind this normally should
not exceed the number of CPU cores available. The values in the examples
below are arbitrary and not particular recommendations.
Each thread requires a Redis connection, so adding threads may increase Redis
latency and potentially cause client timeouts. See the [Sidekiq documentation
about Redis](https://github.com/mperham/sidekiq/wiki/Using-Redis) for more
details.
### When running a single Sidekiq process (default)
1. Edit `/etc/gitlab/gitlab.rb` and add:
......@@ -140,11 +149,14 @@ To limit the concurrency of the Sidekiq process:
sudo gitlab-ctl reconfigure
```
To limit the max concurrency of the Sidekiq cluster processes:
This will set the concurrency (number of threads) for the Sidekiq process.
### When running Sidekiq cluster
1. Edit `/etc/gitlab/gitlab.rb` and add:
```ruby
sidekiq_cluster['min_concurrency'] = 15
sidekiq_cluster['max_concurrency'] = 25
```
......@@ -154,14 +166,21 @@ To limit the max concurrency of the Sidekiq cluster processes:
sudo gitlab-ctl reconfigure
```
For each queue group, the concurrency factor will be set to `min(number of queues, N)`.
Setting the value to 0 will disable the limit. Keep in mind this normally would
not exceed the number of CPU cores available.
`min_concurrency` and `max_concurrency` are independent; one can be set without
the other. Setting `min_concurrency` to 0 will disable the limit.
For each queue group, let N be one more than the number of queues. The
concurrency factor will be set to:
1. `N`, if it's between `min_concurrency` and `max_concurrency`.
1. `max_concurrency`, if `N` exceeds this value.
1. `min_concurrency`, if `N` is less than this value.
If `min_concurrency` is equal to `max_concurrency`, then this value will be used
regardless of the number of queues.
Each thread requires a Redis connection, so adding threads may
increase Redis latency and potentially cause client timeouts. See the [Sidekiq
documentation about Redis](https://github.com/mperham/sidekiq/wiki/Using-Redis)
for more details.
When `min_concurrency` is greater than `max_concurrency`, it is treated as
being equal to `max_concurrency`.
## Modifying the check interval
......
......@@ -2,7 +2,7 @@
>**Note:** This feature was introduced in GitLab 8.11
**Valid access levels**
## Valid access levels
The access levels are defined in the `Gitlab::Access` module. Currently, these levels are recognized:
......
......@@ -2434,6 +2434,11 @@ type GrafanaIntegration {
}
type Group {
"""
Indicates whether Auto DevOps is enabled for all projects within this group
"""
autoDevopsEnabled: Boolean
"""
Avatar URL of the group
"""
......@@ -2449,6 +2454,11 @@ type Group {
"""
descriptionHtml: String
"""
Indicates if a group has email notifications disabled
"""
emailsDisabled: Boolean
"""
Find a single epic
"""
......@@ -2623,6 +2633,11 @@ type Group {
"""
path: String!
"""
The permission level required to create projects in the group
"""
projectCreationLevel: String
"""
Projects within this namespace
"""
......@@ -2658,11 +2673,26 @@ type Group {
"""
requestAccessEnabled: Boolean
"""
Indicates if all users in this group are required to set up two-factor authentication
"""
requireTwoFactorAuthentication: Boolean
"""
Aggregated storage statistics of the namespace. Only available for root namespaces
"""
rootStorageStatistics: RootStorageStatistics
"""
Indicates if sharing a project with another group within this group is prevented
"""
shareWithGroupLock: Boolean
"""
The permission level required to create subgroups within the group
"""
subgroupCreationLevel: String
"""
Time logged in issues by group members
"""
......@@ -2698,6 +2728,11 @@ type Group {
startDate: Time!
): TimelogConnection!
"""
Time before two-factor authentication is enforced
"""
twoFactorGracePeriod: Int
"""
Permissions for the current user on the resource
"""
......
......@@ -3044,6 +3044,20 @@
"name": "Group",
"description": null,
"fields": [
{
"name": "autoDevopsEnabled",
"description": "Indicates whether Auto DevOps is enabled for all projects within this group",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "avatarUrl",
"description": "Avatar URL of the group",
......@@ -3086,6 +3100,20 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "emailsDisabled",
"description": "Indicates if a group has email notifications disabled",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "epic",
"description": "Find a single epic",
......@@ -3524,6 +3552,20 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "projectCreationLevel",
"description": "The permission level required to create projects in the group",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "projects",
"description": "Projects within this namespace",
......@@ -3605,6 +3647,20 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "requireTwoFactorAuthentication",
"description": "Indicates if all users in this group are required to set up two-factor authentication",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "rootStorageStatistics",
"description": "Aggregated storage statistics of the namespace. Only available for root namespaces",
......@@ -3619,6 +3675,34 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "shareWithGroupLock",
"description": "Indicates if sharing a project with another group within this group is prevented",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "subgroupCreationLevel",
"description": "The permission level required to create subgroups within the group",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "timelogs",
"description": "Time logged in issues by group members",
......@@ -3704,6 +3788,20 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "twoFactorGracePeriod",
"description": "Time before two-factor authentication is enforced",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "userPermissions",
"description": "Permissions for the current user on the resource",
......
......@@ -393,6 +393,13 @@ Autogenerated return type of EpicTreeReorder
| `userPermissions` | GroupPermissions! | Permissions for the current user on the resource |
| `webUrl` | String! | Web URL of the group |
| `avatarUrl` | String | Avatar URL of the group |
| `shareWithGroupLock` | Boolean | Indicates if sharing a project with another group within this group is prevented |
| `projectCreationLevel` | String | The permission level required to create projects in the group |
| `subgroupCreationLevel` | String | The permission level required to create subgroups within the group |
| `requireTwoFactorAuthentication` | Boolean | Indicates if all users in this group are required to set up two-factor authentication |
| `twoFactorGracePeriod` | Int | Time before two-factor authentication is enforced |
| `autoDevopsEnabled` | Boolean | Indicates whether Auto DevOps is enabled for all projects within this group |
| `emailsDisabled` | Boolean | Indicates if a group has email notifications disabled |
| `mentionsDisabled` | Boolean | Indicates if a group is disabled from getting mentioned |
| `parent` | Group | Parent group |
| `epicsEnabled` | Boolean | Indicates if Epics are enabled for namespace |
......
......@@ -1405,3 +1405,53 @@ Example response:
```
Please note that `last_activity_at` is deprecated, please use `last_activity_on`.
## User memberships (admin only)
> [Introduced](https://gitlab.com/gitlab-org/gitlab/merge_requests/22518) in GitLab 12.8.
Lists all projects and groups a user is a member of. This endpoint is available for admins only.
It returns the `source_id`, `source_name`, `source_type` and `access_level` of a membership.
Source can be of type `Namespace` (representing a group) or `Project`. The response represents only direct memberships. Inherited memberships, for example in subgroups, will not be included.
Access levels will be represented by an integer value. Read more about the meaning of access level values [here](access_requests.md#valid-access-levels).
```
GET /users/:id/memberships
```
Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | The ID of a specified user |
| `type` | string | no | Filter memberships by type. Can be either `Project` or `Namespace` |
Returns:
- `200 OK` on success.
- `404 User Not Found` if user cannot be found.
- `403 Forbidden` when not requested by an admin.
- `400 Bad Request` when requested type is not supported.
```bash
curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/users/<user_id>/memberships
```
Example response:
```json
[
{
"source_id": 1,
"source_name": "Project one",
"source_type": "Project",
"access_level": "20"
},
{
"source_id": 3,
"source_name": "Group three",
"source_type": "Namespace",
"access_level": "20"
},
]
```
......@@ -2,6 +2,15 @@
module API
module Entities
class Membership < Grape::Entity
expose :source_id
expose :source_name do |member|
member.source.name
end
expose :source_type
expose :access_level
end
class BlameRangeCommit < Grape::Entity
expose :id
expose :parent_ids
......
......@@ -533,6 +533,32 @@ module API
end
# rubocop: enable CodeReuse/ActiveRecord
desc 'Get memberships' do
success Entities::Membership
end
params do
requires :user_id, type: Integer, desc: 'The ID of the user'
optional :type, type: String, values: %w[Project Namespace]
use :pagination
end
get ":user_id/memberships" do
authenticated_as_admin!
user = find_user_by_id(params)
members = case params[:type]
when 'Project'
user.project_members
when 'Namespace'
user.group_members
else
user.members
end
members = members.including_source
present paginate(members), with: Entities::Membership
end
params do
requires :user_id, type: Integer, desc: 'The ID of the user'
end
......
......@@ -54,6 +54,12 @@ module Sentry
end
end
def http_post(url, params = {})
http_request do
Gitlab::HTTP.post(url, **request_params.merge(body: params.to_json))
end
end
def http_request
response = handle_request_exceptions do
yield
......
......@@ -3,8 +3,22 @@
module Sentry
class Client
module IssueLink
def create_issue_link(integration_id, sentry_issue_identifier, issue)
issue_link_url = issue_link_api_url(integration_id, sentry_issue_identifier)
# Creates a link in Sentry corresponding to the provided
# Sentry issue and GitLab issue
# @param integration_id [Integer, nil] Representing a global
# GitLab integration in Sentry. Nil for plugins.
# @param sentry_issue_id [Integer] Id for an issue from Sentry
# @param issue [Issue] Issue for which the link should be created
def create_issue_link(integration_id, sentry_issue_id, issue)
return create_plugin_link(sentry_issue_id, issue) unless integration_id
create_global_integration_link(integration_id, sentry_issue_id, issue)
end
private
def create_global_integration_link(integration_id, sentry_issue_id, issue)
issue_link_url = global_integration_link_api_url(integration_id, sentry_issue_id)
params = {
project: issue.project.id,
......@@ -14,11 +28,22 @@ module Sentry
http_put(issue_link_url, params)
end
private
def global_integration_link_api_url(integration_id, sentry_issue_id)
issue_link_url = URI(url)
issue_link_url.path = "/api/0/groups/#{sentry_issue_id}/integrations/#{integration_id}/"
issue_link_url
end
def create_plugin_link(sentry_issue_id, issue)
issue_link_url = plugin_link_api_url(sentry_issue_id)
http_post(issue_link_url, issue_id: issue.iid)
end
def issue_link_api_url(integration_id, sentry_issue_identifier)
def plugin_link_api_url(sentry_issue_id)
issue_link_url = URI(url)
issue_link_url.path = "/api/0/groups/#{sentry_issue_identifier}/integrations/#{integration_id}/"
issue_link_url.path = "/api/0/issues/#{sentry_issue_id}/plugins/gitlab/link/"
issue_link_url
end
......
......@@ -13026,6 +13026,12 @@ msgstr ""
msgid "PackageRegistry|There was a problem fetching the details for this package."
msgstr ""
msgid "PackageRegistry|There was an error fetching the pipeline information."
msgstr ""
msgid "PackageRegistry|Unable to fetch pipeline information"
msgstr ""
msgid "PackageRegistry|Unable to load package"
msgstr ""
......
......@@ -66,14 +66,23 @@ module QA
metadata[:type] = :feature
end
config.before do
config.before(:suite) do
unless browser.rspec_configured
browser.rspec_configured = true
##
# Perform before hooks, which are different for CE and EE
#
Runtime::Release.perform_before_hooks
begin
Runtime::Release.perform_before_hooks
rescue
saved = Capybara::Screenshot.screenshot_and_save_page
QA::Runtime::Logger.error("Screenshot: #{saved[:image]}") if saved&.key?(:image)
QA::Runtime::Logger.error("HTML capture: #{saved[:html]}") if saved&.key?(:html)
raise
end
end
end
end
......
......@@ -3,7 +3,7 @@
FactoryBot.define do
factory :project_error_tracking_setting, class: 'ErrorTracking::ProjectErrorTrackingSetting' do
project
api_url { 'https://sentrytest.gitlab.com/api/0/projects/sentry-org/sentry-project' }
api_url { 'https://gitlab.com/api/0/projects/sentry-org/sentry-project' }
enabled { true }
token { 'access_token_123' }
project_name { 'Sentry Project' }
......
# frozen_string_literal: true
require 'spec_helper'
describe 'View error details page', :js, :use_clean_rails_memory_store_caching, :sidekiq_inline do
include_context 'sentry error tracking context feature'
context 'with current user as project owner' do
before do
sign_in(project.owner)
visit details_project_error_tracking_index_path(project, issue_id: issue_id)
end
it_behaves_like 'error tracking show page'
end
context 'with current user as project guest' do
let_it_be(:user) { create(:user) }
before do
project.add_guest(user)
sign_in(user)
visit details_project_error_tracking_index_path(project, issue_id: issue_id)
end
it 'renders not found' do
expect(page).to have_content('Page Not Found')
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe 'View error details page', :js, :use_clean_rails_memory_store_caching, :sidekiq_inline do
include_context 'sentry error tracking context feature'
let_it_be(:issues_response_body) { fixture_file('sentry/issues_sample_response.json') }
let_it_be(:issues_response) { JSON.parse(issues_response_body) }
let(:issues_api_url) { "#{sentry_api_urls.issues_url}?limit=20&query=is:unresolved" }
before do
stub_request(:get, issues_api_url).with(
headers: { 'Authorization' => 'Bearer access_token_123' }
).to_return(status: 200, body: issues_response_body, headers: { 'Content-Type' => 'application/json' })
end
context 'with current user as project owner' do
before do
sign_in(project.owner)
visit project_error_tracking_index_path(project)
end
it_behaves_like 'error tracking index page'
end
# A bug caused the detail link to be broken for all users but the project owner
context 'with current user as project maintainer' do
let_it_be(:user) { create(:user) }
before do
project.add_maintainer(user)
sign_in(user)
visit project_error_tracking_index_path(project)
end
it_behaves_like 'error tracking index page'
end
context 'with error tracking settings disabled' do
before do
project_error_tracking_settings.update(enabled: false)
sign_in(project.owner)
visit project_error_tracking_index_path(project)
end
it 'renders call to action' do
expect(page).to have_content('Enable error tracking')
end
end
context 'with current user as project guest' do
let_it_be(:user) { create(:user) }
before do
project.add_guest(user)
sign_in(user)
visit project_error_tracking_index_path(project)
end
it 'renders not found' do
expect(page).to have_content('Page Not Found')
end
end
end
{
"type": "object",
"properties" : {
"source_id": { "type": "integer" },
"source_name": { "type": "string" },
"source_type": { "type": "string" },
"access_level": { "type": "integer" }
},
"additionalProperties": false
}
{
"type": "array",
"items": { "$ref": "membership.json" }
}
......@@ -38,7 +38,7 @@
},
"firstSeen": "2018-11-06T21:19:55Z",
"hasSeen": false,
"id": "11",
"id": "503504",
"isBookmarked": false,
"isPublic": false,
"isSubscribed": true,
......@@ -72,64 +72,232 @@
"shortId": "PUMP-STATION-1",
"stats": {
"24h": [
[1541451600.0, 557],
[1541455200.0, 473],
[1541458800.0, 914],
[1541462400.0, 991],
[1541466000.0, 925],
[1541469600.0, 881],
[1541473200.0, 182],
[1541476800.0, 490],
[1541480400.0, 820],
[1541484000.0, 322],
[1541487600.0, 836],
[1541491200.0, 565],
[1541494800.0, 758],
[1541498400.0, 880],
[1541502000.0, 677],
[1541505600.0, 381],
[1541509200.0, 814],
[1541512800.0, 329],
[1541516400.0, 446],
[1541520000.0, 731],
[1541523600.0, 111],
[1541527200.0, 926],
[1541530800.0, 772],
[1541534400.0, 400],
[1541538000.0, 943]
[
1541451600.0,
557
],
[
1541455200.0,
473
],
[
1541458800.0,
914
],
[
1541462400.0,
991
],
[
1541466000.0,
925
],
[
1541469600.0,
881
],
[
1541473200.0,
182
],
[
1541476800.0,
490
],
[
1541480400.0,
820
],
[
1541484000.0,
322
],
[
1541487600.0,
836
],
[
1541491200.0,
565
],
[
1541494800.0,
758
],
[
1541498400.0,
880
],
[
1541502000.0,
677
],
[
1541505600.0,
381
],
[
1541509200.0,
814
],
[
1541512800.0,
329
],
[
1541516400.0,
446
],
[
1541520000.0,
731
],
[
1541523600.0,
111
],
[
1541527200.0,
926
],
[
1541530800.0,
772
],
[
1541534400.0,
400
],
[
1541538000.0,
943
]
],
"30d": [
[1538870400.0, 565],
[1538956800.0, 12862],
[1539043200.0, 15617],
[1539129600.0, 10809],
[1539216000.0, 15065],
[1539302400.0, 12927],
[1539388800.0, 12994],
[1539475200.0, 13139],
[1539561600.0, 11838],
[1539648000.0, 12088],
[1539734400.0, 12338],
[1539820800.0, 12768],
[1539907200.0, 12816],
[1539993600.0, 15356],
[1540080000.0, 10910],
[1540166400.0, 12306],
[1540252800.0, 12912],
[1540339200.0, 14700],
[1540425600.0, 11890],
[1540512000.0, 11684],
[1540598400.0, 13510],
[1540684800.0, 12625],
[1540771200.0, 12811],
[1540857600.0, 13180],
[1540944000.0, 14651],
[1541030400.0, 14161],
[1541116800.0, 12612],
[1541203200.0, 14316],
[1541289600.0, 14742],
[1541376000.0, 12505],
[1541462400.0, 14180]
[
1538870400.0,
565
],
[
1538956800.0,
12862
],
[
1539043200.0,
15617
],
[
1539129600.0,
10809
],
[
1539216000.0,
15065
],
[
1539302400.0,
12927
],
[
1539388800.0,
12994
],
[
1539475200.0,
13139
],
[
1539561600.0,
11838
],
[
1539648000.0,
12088
],
[
1539734400.0,
12338
],
[
1539820800.0,
12768
],
[
1539907200.0,
12816
],
[
1539993600.0,
15356
],
[
1540080000.0,
10910
],
[
1540166400.0,
12306
],
[
1540252800.0,
12912
],
[
1540339200.0,
14700
],
[
1540425600.0,
11890
],
[
1540512000.0,
11684
],
[
1540598400.0,
13510
],
[
1540684800.0,
12625
],
[
1540771200.0,
12811
],
[
1540857600.0,
13180
],
[
1540944000.0,
14651
],
[
1541030400.0,
14161
],
[
1541116800.0,
12612
],
[
1541203200.0,
14316
],
[
1541289600.0,
14742
],
[
1541376000.0,
12505
],
[
1541462400.0,
14180
]
]
},
"status": "unresolved",
......
{
"message": "Successfully linked issue.",
"link": "https://gitlab.com/test/tanuki-inc/issues/3",
"id": 3,
"label": "GL-3"
}
......@@ -10,7 +10,14 @@ describe GitlabSchema.types['Group'] do
it { expect(described_class).to require_graphql_authorizations(:read_group) }
it 'has the expected fields' do
expected_fields = %w[web_url avatar_url mentions_disabled parent]
expected_fields = %w[
id name path full_name full_path description description_html visibility
lfs_enabled request_access_enabled projects root_storage_statistics
web_url avatar_url share_with_group_lock project_creation_level
subgroup_creation_level require_two_factor_authentication
two_factor_grace_period auto_devops_enabled emails_disabled
mentions_disabled parent
]
is_expected.to include_graphql_fields(*expected_fields)
end
......
......@@ -5,18 +5,18 @@ require 'spec_helper'
describe Sentry::Client::IssueLink do
include SentryClientHelpers
let(:error_tracking_setting) { create(:project_error_tracking_setting, api_url: sentry_url) }
let(:sentry_url) { 'https://sentrytest.gitlab.com/api/0/projects/sentry-org/sentry-project' }
let(:client) { error_tracking_setting.sentry_client }
let_it_be(:sentry_url) { 'https://sentrytest.gitlab.com/api/0/projects/sentry-org/sentry-project' }
let_it_be(:error_tracking_setting) { create(:project_error_tracking_setting, api_url: sentry_url) }
let_it_be(:issue) { create(:issue, project: error_tracking_setting.project) }
let(:issue_link_sample_response) { JSON.parse(fixture_file('sentry/issue_link_sample_response.json')) }
let(:client) { error_tracking_setting.sentry_client }
let(:sentry_issue_id) { 11111111 }
describe '#create_issue_link' do
let(:sentry_issue_link_url) { "https://sentrytest.gitlab.com/api/0/groups/#{sentry_issue_id}/integrations/#{integration_id}/" }
let(:integration_id) { 44444 }
let(:sentry_issue_id) { 11111111 }
let(:issue) { create(:issue, project: error_tracking_setting.project) }
let(:sentry_issue_link_url) { "https://sentrytest.gitlab.com/api/0/groups/#{sentry_issue_id}/integrations/#{integration_id}/" }
let(:issue_link_sample_response) { JSON.parse(fixture_file('sentry/global_integration_link_sample_response.json')) }
let(:sentry_api_response) { issue_link_sample_response }
let!(:sentry_api_request) { stub_sentry_request(sentry_issue_link_url, :put, body: sentry_api_response, status: 201) }
......@@ -37,5 +37,29 @@ describe Sentry::Client::IssueLink do
it_behaves_like 'maps Sentry exceptions', :put
end
context 'when integration_id is not provided' do
let(:sentry_issue_link_url) { "https://sentrytest.gitlab.com/api/0/issues/#{sentry_issue_id}/plugins/gitlab/link/" }
let(:integration_id) { nil }
let(:issue_link_sample_response) { JSON.parse(fixture_file('sentry/plugin_link_sample_response.json')) }
let!(:sentry_api_request) { stub_sentry_request(sentry_issue_link_url, :post, body: sentry_api_response) }
it_behaves_like 'calls sentry api'
it { is_expected.to be_present }
context 'redirects' do
let(:sentry_api_url) { sentry_issue_link_url }
it_behaves_like 'no Sentry redirects', :post
end
context 'when exception is raised' do
let(:sentry_request_url) { sentry_issue_link_url }
it_behaves_like 'maps Sentry exceptions', :post
end
end
end
end
......@@ -8,7 +8,7 @@ describe Sentry::Client::Issue do
let(:token) { 'test-token' }
let(:sentry_url) { 'https://sentrytest.gitlab.com/api/0' }
let(:client) { Sentry::Client.new(sentry_url, token) }
let(:issue_id) { 11 }
let(:issue_id) { 503504 }
describe '#list_issues' do
shared_examples 'issues have correct return type' do |klass|
......@@ -243,7 +243,7 @@ describe Sentry::Client::Issue do
end
it 'has a correct external URL' do
expect(subject.external_url).to eq('https://sentrytest.gitlab.com/api/0/issues/11')
expect(subject.external_url).to eq('https://sentrytest.gitlab.com/api/0/issues/503504')
end
it 'issue has a correct external base url' do
......
......@@ -2100,6 +2100,83 @@ describe API::Users do
end
end
describe "GET /users/:id/memberships" do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:group) { create(:group) }
let(:requesting_user) { create(:user) }
before_all do
project.add_guest(user)
group.add_guest(user)
end
it "responses with 403" do
get api("/users/#{user.id}/memberships", requesting_user)
expect(response).to have_gitlab_http_status(:forbidden)
end
context 'requested by admin user' do
let(:requesting_user) { create(:user, :admin) }
it "responses successfully" do
get api("/users/#{user.id}/memberships", requesting_user)
aggregate_failures 'expect successful response including groups and projects' do
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('public_api/v4/memberships')
expect(response).to include_pagination_headers
expect(json_response).to contain_exactly(
a_hash_including('source_type' => 'Project'),
a_hash_including('source_type' => 'Namespace')
)
end
end
it 'does not submit N+1 DB queries' do
# Avoid setup queries
get api("/users/#{user.id}/memberships", requesting_user)
control = ActiveRecord::QueryRecorder.new do
get api("/users/#{user.id}/memberships", requesting_user)
end
create_list(:project, 5).map { |project| project.add_guest(user) }
expect do
get api("/users/#{user.id}/memberships", requesting_user)
end.not_to exceed_query_limit(control)
end
context 'with type filter' do
it "only returns project memberships" do
get api("/users/#{user.id}/memberships?type=Project", requesting_user)
aggregate_failures do
expect(json_response).to contain_exactly(a_hash_including('source_type' => 'Project'))
expect(json_response).not_to include(a_hash_including('source_type' => 'Namespace'))
end
end
it "only returns group memberships" do
get api("/users/#{user.id}/memberships?type=Namespace", requesting_user)
aggregate_failures do
expect(json_response).to contain_exactly(a_hash_including('source_type' => 'Namespace'))
expect(json_response).not_to include(a_hash_including('source_type' => 'Project'))
end
end
it "recognizes unsupported types" do
get api("/users/#{user.id}/memberships?type=foo", requesting_user)
expect(response).to have_gitlab_http_status(:bad_request)
end
end
end
end
context "user activities", :clean_gitlab_redis_shared_state do
let!(:old_active_user) { create(:user, last_activity_on: Time.utc(2000, 1, 1)) }
let!(:newly_active_user) { create(:user, last_activity_on: 2.days.ago.midday) }
......
# frozen_string_literal: true
shared_context 'sentry error tracking context feature' do
include ReactiveCachingHelpers
let_it_be(:project) { create(:project) }
let_it_be(:project_error_tracking_settings) { create(:project_error_tracking_setting, project: project) }
let_it_be(:issue_response_body) { fixture_file('sentry/issue_sample_response.json') }
let_it_be(:issue_response) { JSON.parse(issue_response_body) }
let_it_be(:event_response_body) { fixture_file('sentry/issue_latest_event_sample_response.json') }
let_it_be(:event_response) { JSON.parse(event_response_body) }
let(:sentry_api_urls) { Sentry::ApiUrls.new(project_error_tracking_settings.api_url) }
let(:issue_id) { issue_response['id'] }
before do
stub_request(:get, sentry_api_urls.issue_url(issue_id)).with(
headers: { 'Authorization' => 'Bearer access_token_123' }
).to_return(status: 200, body: issue_response_body, headers: { 'Content-Type' => 'application/json' })
stub_request(:get, sentry_api_urls.issue_latest_event_url(issue_id)).with(
headers: { 'Authorization' => 'Bearer access_token_123' }
).to_return(status: 200, body: event_response_body, headers: { 'Content-Type' => 'application/json' })
end
end
# frozen_string_literal: true
shared_examples 'error tracking index page' do
it 'renders the error index page' do
within('div.js-title-container') do
expect(page).to have_content(project.namespace.name)
expect(page).to have_content(project.name)
end
within('div.error-list') do
expect(page).to have_content('Error')
expect(page).to have_content('Events')
expect(page).to have_content('Users')
expect(page).to have_content('Last Seen')
end
end
it 'renders the error index data' do
Timecop.freeze(2020, 01, 01, 12, 0, 0) do
within('div.error-list') do
expect(page).to have_content(issues_response[0]['title'])
expect(page).to have_content(issues_response[0]['count'].to_s)
expect(page).to have_content(issues_response[0]['last_seen'])
expect(page).to have_content('1 year ago')
end
end
end
context 'when error is clicked' do
before do
click_on issues_response[0]['title']
end
it 'loads the error page' do
expect(page).to have_content('Error details')
end
end
end
shared_examples 'expanded stack trace context' do |selected_line: nil, expected_line: 1|
it 'expands the stack trace context' do
within('div.stacktrace') do
find("div.file-holder:nth-child(#{selected_line}) svg.ic-chevron-right").click if selected_line
expanded_line = find("div.file-holder:nth-child(#{expected_line})")
expect(expanded_line).to have_css('svg.ic-chevron-down')
event_response['entries'][0]['data']['values'][0]['stacktrace']['frames'][-expected_line]['context'].each do |context|
expect(page).to have_content(context[0])
end
end
end
end
shared_examples 'error tracking show page' do
it 'renders the error details' do
release_short_version = issue_response['firstRelease']['shortVersion']
Timecop.freeze(2020, 01, 01, 12, 0, 0) do
expect(page).to have_content('1 month ago by raven.scripts.runner in main')
expect(page).to have_content(issue_response['metadata']['title'])
expect(page).to have_content('level: error')
expect(page).to have_content('Error details')
expect(page).to have_content('GitLab Issue: https://gitlab.com/gitlab-org/gitlab/issues/1')
expect(page).to have_content("Sentry event: https://sentrytest.gitlab.com/sentry-org/sentry-project/issues/#{issue_id}")
expect(page).to have_content("First seen: 1 year ago (2018-11-06 9:19:55PM UTC) Release: #{release_short_version}")
expect(page).to have_content('Events: 1')
expect(page).to have_content('Users: 0')
end
end
it 'renders the stack trace heading' do
expect(page).to have_content('Stack trace')
end
it 'renders the stack trace' do
event_response['entries'][0]['data']['values'][0]['stacktrace']['frames'].each do |frame|
expect(frame['filename']).not_to be_nil
expect(page).to have_content(frame['filename'])
end
end
# The first line is expanded by default if no line is selected
it_behaves_like 'expanded stack trace context', selected_line: nil, expected_line: 1
it_behaves_like 'expanded stack trace context', selected_line: 8, expected_line: 8
end
......@@ -40,14 +40,17 @@ describe ErrorTrackingIssueLinkWorker do
end
end
shared_examples_for 'terminates after one API request' do
shared_examples_for 'attempts to create a link via plugin' do
it 'takes no action' do
expect_next_instance_of(Sentry::Client) do |client|
expect(client).to receive(:repos).with('sentry-org').and_return([repo])
expect(client)
.to receive(:create_issue_link)
.with(nil, sentry_issue.sentry_issue_identifier, issue)
.and_return(true)
end
expect_any_instance_of(Sentry::Client).not_to receive(:create_issue_link)
expect(subject).to be nil
expect(subject).to be true
end
end
......@@ -78,7 +81,7 @@ describe ErrorTrackingIssueLinkWorker do
)
end
it_behaves_like 'terminates after one API request'
it_behaves_like 'attempts to create a link via plugin'
end
context 'when Sentry the GitLab integration is for another project' do
......@@ -90,7 +93,7 @@ describe ErrorTrackingIssueLinkWorker do
)
end
it_behaves_like 'terminates after one API request'
it_behaves_like 'attempts to create a link via plugin'
end
end
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