Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
540c69c58c
commit
c4b69460e8
53 changed files with 576 additions and 152 deletions
|
@ -522,3 +522,9 @@ Migration/ComplexIndexesRequireName:
|
||||||
Exclude:
|
Exclude:
|
||||||
- !ruby/regexp /\Adb\/(post_)?migrate\/201.*\.rb\z/
|
- !ruby/regexp /\Adb\/(post_)?migrate\/201.*\.rb\z/
|
||||||
- !ruby/regexp /\Adb\/(post_)?migrate\/20200[1-7].*\.rb\z/
|
- !ruby/regexp /\Adb\/(post_)?migrate\/20200[1-7].*\.rb\z/
|
||||||
|
|
||||||
|
Migration/ReferToIndexByName:
|
||||||
|
Exclude:
|
||||||
|
- !ruby/regexp /\Adb\/(post_)?migrate\/201.*\.rb\z/
|
||||||
|
- !ruby/regexp /\Adb\/(post_)?migrate\/20200[1-7].*\.rb\z/
|
||||||
|
- !ruby/regexp /\Aee\/db\/geo\/(post_)?migrate\/201.*\.rb\z/
|
||||||
|
|
|
@ -149,14 +149,13 @@ export default {
|
||||||
|
|
||||||
<span class="commit-row-message d-block d-sm-none">· {{ commit.short_id }}</span>
|
<span class="commit-row-message d-block d-sm-none">· {{ commit.short_id }}</span>
|
||||||
|
|
||||||
<button
|
<gl-button
|
||||||
v-if="commit.description_html && collapsible"
|
v-if="commit.description_html && collapsible"
|
||||||
class="text-expander js-toggle-button"
|
class="js-toggle-button"
|
||||||
type="button"
|
size="small"
|
||||||
|
icon="ellipsis_h"
|
||||||
:aria-label="__('Toggle commit description')"
|
:aria-label="__('Toggle commit description')"
|
||||||
>
|
/>
|
||||||
<gl-icon :size="12" name="ellipsis_h" />
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div class="committer">
|
<div class="committer">
|
||||||
<a
|
<a
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
/* eslint-disable @gitlab/vue-require-i18n-strings */
|
/* eslint-disable @gitlab/vue-require-i18n-strings */
|
||||||
import { GlLoadingIcon } from '@gitlab/ui';
|
import { GlLoadingIcon, GlButton } from '@gitlab/ui';
|
||||||
import { deprecatedCreateFlash as Flash } from '~/flash';
|
import { deprecatedCreateFlash as Flash } from '~/flash';
|
||||||
import tooltip from '~/vue_shared/directives/tooltip';
|
import tooltip from '~/vue_shared/directives/tooltip';
|
||||||
import { s__, __ } from '~/locale';
|
import { s__, __ } from '~/locale';
|
||||||
|
@ -19,6 +19,7 @@ export default {
|
||||||
statusIcon,
|
statusIcon,
|
||||||
ClipboardButton,
|
ClipboardButton,
|
||||||
GlLoadingIcon,
|
GlLoadingIcon,
|
||||||
|
GlButton,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
mr: {
|
mr: {
|
||||||
|
@ -112,48 +113,52 @@ export default {
|
||||||
:date-title="mr.metrics.mergedAt"
|
:date-title="mr.metrics.mergedAt"
|
||||||
:date-readable="mr.metrics.readableMergedAt"
|
:date-readable="mr.metrics.readableMergedAt"
|
||||||
/>
|
/>
|
||||||
<a
|
<gl-button
|
||||||
v-if="mr.canRevertInCurrentMR"
|
v-if="mr.canRevertInCurrentMR"
|
||||||
v-tooltip
|
v-tooltip
|
||||||
:title="revertTitle"
|
:title="revertTitle"
|
||||||
class="btn btn-close btn-sm"
|
size="small"
|
||||||
|
category="secondary"
|
||||||
|
variant="warning"
|
||||||
href="#modal-revert-commit"
|
href="#modal-revert-commit"
|
||||||
data-toggle="modal"
|
data-toggle="modal"
|
||||||
data-container="body"
|
data-container="body"
|
||||||
>
|
>
|
||||||
{{ revertLabel }}
|
{{ revertLabel }}
|
||||||
</a>
|
</gl-button>
|
||||||
<a
|
<gl-button
|
||||||
v-else-if="mr.revertInForkPath"
|
v-else-if="mr.revertInForkPath"
|
||||||
v-tooltip
|
v-tooltip
|
||||||
:href="mr.revertInForkPath"
|
:href="mr.revertInForkPath"
|
||||||
:title="revertTitle"
|
:title="revertTitle"
|
||||||
class="btn btn-close btn-sm"
|
size="small"
|
||||||
|
category="secondary"
|
||||||
|
variant="warning"
|
||||||
data-method="post"
|
data-method="post"
|
||||||
>
|
>
|
||||||
{{ revertLabel }}
|
{{ revertLabel }}
|
||||||
</a>
|
</gl-button>
|
||||||
<a
|
<gl-button
|
||||||
v-if="mr.canCherryPickInCurrentMR"
|
v-if="mr.canCherryPickInCurrentMR"
|
||||||
v-tooltip
|
v-tooltip
|
||||||
:title="cherryPickTitle"
|
:title="cherryPickTitle"
|
||||||
class="btn btn-default btn-sm"
|
size="small"
|
||||||
href="#modal-cherry-pick-commit"
|
href="#modal-cherry-pick-commit"
|
||||||
data-toggle="modal"
|
data-toggle="modal"
|
||||||
data-container="body"
|
data-container="body"
|
||||||
>
|
>
|
||||||
{{ cherryPickLabel }}
|
{{ cherryPickLabel }}
|
||||||
</a>
|
</gl-button>
|
||||||
<a
|
<gl-button
|
||||||
v-else-if="mr.cherryPickInForkPath"
|
v-else-if="mr.cherryPickInForkPath"
|
||||||
v-tooltip
|
v-tooltip
|
||||||
:href="mr.cherryPickInForkPath"
|
:href="mr.cherryPickInForkPath"
|
||||||
:title="cherryPickTitle"
|
:title="cherryPickTitle"
|
||||||
class="btn btn-default btn-sm"
|
size="small"
|
||||||
data-method="post"
|
data-method="post"
|
||||||
>
|
>
|
||||||
{{ cherryPickLabel }}
|
{{ cherryPickLabel }}
|
||||||
</a>
|
</gl-button>
|
||||||
</div>
|
</div>
|
||||||
<section class="mr-info-list" data-qa-selector="merged_status_content">
|
<section class="mr-info-list" data-qa-selector="merged_status_content">
|
||||||
<p>
|
<p>
|
||||||
|
@ -181,14 +186,14 @@ export default {
|
||||||
</p>
|
</p>
|
||||||
<p v-if="shouldShowRemoveSourceBranch" class="space-children">
|
<p v-if="shouldShowRemoveSourceBranch" class="space-children">
|
||||||
<span>{{ s__('mrWidget|You can delete the source branch now') }}</span>
|
<span>{{ s__('mrWidget|You can delete the source branch now') }}</span>
|
||||||
<button
|
<gl-button
|
||||||
:disabled="isMakingRequest"
|
:disabled="isMakingRequest"
|
||||||
type="button"
|
size="small"
|
||||||
class="btn btn-sm btn-default js-remove-branch-button"
|
class="js-remove-branch-button"
|
||||||
@click="removeSourceBranch"
|
@click="removeSourceBranch"
|
||||||
>
|
>
|
||||||
{{ s__('mrWidget|Delete source branch') }}
|
{{ s__('mrWidget|Delete source branch') }}
|
||||||
</button>
|
</gl-button>
|
||||||
</p>
|
</p>
|
||||||
<p v-if="shouldShowSourceBranchRemoving">
|
<p v-if="shouldShowSourceBranchRemoving">
|
||||||
<gl-loading-icon :inline="true" />
|
<gl-loading-icon :inline="true" />
|
||||||
|
|
|
@ -156,13 +156,13 @@ class SessionsController < Devise::SessionsController
|
||||||
(options = request.env["warden.options"]) && options[:action] == "unauthenticated"
|
(options = request.env["warden.options"]) && options[:action] == "unauthenticated"
|
||||||
end
|
end
|
||||||
|
|
||||||
# storing sessions per IP lets us check if there are associated multiple
|
# counting sessions per IP lets us check if there are associated multiple
|
||||||
# anonymous sessions with one IP and prevent situations when there are
|
# anonymous sessions with one IP and prevent situations when there are
|
||||||
# multiple attempts of logging in
|
# multiple attempts of logging in
|
||||||
def store_unauthenticated_sessions
|
def store_unauthenticated_sessions
|
||||||
return if current_user
|
return if current_user
|
||||||
|
|
||||||
Gitlab::AnonymousSession.new(request.remote_ip, session_id: request.session.id).store_session_id_per_ip
|
Gitlab::AnonymousSession.new(request.remote_ip).count_session_ip
|
||||||
end
|
end
|
||||||
|
|
||||||
# Handle an "initial setup" state, where there's only one user, it's an admin,
|
# Handle an "initial setup" state, where there's only one user, it's an admin,
|
||||||
|
@ -280,7 +280,7 @@ class SessionsController < Devise::SessionsController
|
||||||
end
|
end
|
||||||
|
|
||||||
def exceeded_anonymous_sessions?
|
def exceeded_anonymous_sessions?
|
||||||
Gitlab::AnonymousSession.new(request.remote_ip).stored_sessions >= MAX_FAILED_LOGIN_ATTEMPTS
|
Gitlab::AnonymousSession.new(request.remote_ip).session_count >= MAX_FAILED_LOGIN_ATTEMPTS
|
||||||
end
|
end
|
||||||
|
|
||||||
def authentication_method
|
def authentication_method
|
||||||
|
|
|
@ -5,6 +5,8 @@ module Types
|
||||||
graphql_name 'Release'
|
graphql_name 'Release'
|
||||||
description 'Represents a release'
|
description 'Represents a release'
|
||||||
|
|
||||||
|
connection_type_class(Types::CountableConnectionType)
|
||||||
|
|
||||||
authorize :read_release
|
authorize :read_release
|
||||||
|
|
||||||
alias_method :release, :object
|
alias_method :release, :object
|
||||||
|
|
|
@ -563,6 +563,10 @@ class Note < ApplicationRecord
|
||||||
noteable.author if for_personal_snippet?
|
noteable.author if for_personal_snippet?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def skip_notification?
|
||||||
|
review.present?
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
# Using this method followed by a call to `save` may result in ActiveRecord::RecordNotUnique exception
|
# Using this method followed by a call to `save` may result in ActiveRecord::RecordNotUnique exception
|
||||||
|
|
|
@ -5,6 +5,7 @@ class JiraService < IssueTrackerService
|
||||||
include Gitlab::Routing
|
include Gitlab::Routing
|
||||||
include ApplicationHelper
|
include ApplicationHelper
|
||||||
include ActionView::Helpers::AssetUrlHelper
|
include ActionView::Helpers::AssetUrlHelper
|
||||||
|
include Gitlab::Utils::StrongMemoize
|
||||||
|
|
||||||
PROJECTS_PER_PAGE = 50
|
PROJECTS_PER_PAGE = 50
|
||||||
|
|
||||||
|
@ -32,6 +33,7 @@ class JiraService < IssueTrackerService
|
||||||
data_field :username, :password, :url, :api_url, :jira_issue_transition_id, :project_key, :issues_enabled
|
data_field :username, :password, :url, :api_url, :jira_issue_transition_id, :project_key, :issues_enabled
|
||||||
|
|
||||||
before_update :reset_password
|
before_update :reset_password
|
||||||
|
after_commit :update_deployment_type, on: [:create, :update], if: :update_deployment_type?
|
||||||
|
|
||||||
enum comment_detail: {
|
enum comment_detail: {
|
||||||
standard: 1,
|
standard: 1,
|
||||||
|
@ -212,7 +214,7 @@ class JiraService < IssueTrackerService
|
||||||
end
|
end
|
||||||
|
|
||||||
def test(_)
|
def test(_)
|
||||||
result = test_settings
|
result = server_info
|
||||||
success = result.present?
|
success = result.present?
|
||||||
result = @error&.message unless success
|
result = @error&.message unless success
|
||||||
|
|
||||||
|
@ -231,10 +233,10 @@ class JiraService < IssueTrackerService
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def test_settings
|
def server_info
|
||||||
return unless client_url.present?
|
strong_memoize(:server_info) do
|
||||||
|
client_url.present? ? jira_request { client.ServerInfo.all.attrs } : nil
|
||||||
jira_request { client.ServerInfo.all.attrs }
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def can_cross_reference?(noteable)
|
def can_cross_reference?(noteable)
|
||||||
|
@ -436,6 +438,25 @@ class JiraService < IssueTrackerService
|
||||||
url_changed?
|
url_changed?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def update_deployment_type?
|
||||||
|
api_url_changed? || url_changed? || username_changed? || password_changed?
|
||||||
|
end
|
||||||
|
|
||||||
|
def update_deployment_type
|
||||||
|
clear_memoization(:server_info) # ensure we run the request when we try to update deployment type
|
||||||
|
results = server_info
|
||||||
|
return data_fields.deployment_unknown! unless results.present?
|
||||||
|
|
||||||
|
case results['deploymentType']
|
||||||
|
when 'Server'
|
||||||
|
data_fields.deployment_server!
|
||||||
|
when 'Cloud'
|
||||||
|
data_fields.deployment_cloud!
|
||||||
|
else
|
||||||
|
data_fields.deployment_unknown!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def self.event_description(event)
|
def self.event_description(event)
|
||||||
case event
|
case event
|
||||||
when "merge_request", "merge_request_events"
|
when "merge_request", "merge_request_events"
|
||||||
|
|
|
@ -178,10 +178,14 @@ module ObjectStorage
|
||||||
end
|
end
|
||||||
|
|
||||||
def workhorse_authorize(has_length:, maximum_size: nil)
|
def workhorse_authorize(has_length:, maximum_size: nil)
|
||||||
if self.object_store_enabled? && self.direct_upload_enabled?
|
{}.tap do |hash|
|
||||||
{ RemoteObject: workhorse_remote_upload_options(has_length: has_length, maximum_size: maximum_size) }
|
if self.object_store_enabled? && self.direct_upload_enabled?
|
||||||
else
|
hash[:RemoteObject] = workhorse_remote_upload_options(has_length: has_length, maximum_size: maximum_size)
|
||||||
{ TempPath: workhorse_local_upload_path }
|
else
|
||||||
|
hash[:TempPath] = workhorse_local_upload_path
|
||||||
|
end
|
||||||
|
|
||||||
|
hash[:MaximumSize] = maximum_size if maximum_size.present?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -13,17 +13,11 @@ class NewNoteWorker # rubocop:disable Scalability/IdempotentWorker
|
||||||
# rubocop: disable CodeReuse/ActiveRecord
|
# rubocop: disable CodeReuse/ActiveRecord
|
||||||
def perform(note_id, _params = {})
|
def perform(note_id, _params = {})
|
||||||
if note = Note.find_by(id: note_id)
|
if note = Note.find_by(id: note_id)
|
||||||
NotificationService.new.new_note(note) unless skip_notification?(note)
|
NotificationService.new.new_note(note) unless note.skip_notification?
|
||||||
Notes::PostProcessService.new(note).execute
|
Notes::PostProcessService.new(note).execute
|
||||||
else
|
else
|
||||||
Gitlab::AppLogger.error("NewNoteWorker: couldn't find note with ID=#{note_id}, skipping job")
|
Gitlab::AppLogger.error("NewNoteWorker: couldn't find note with ID=#{note_id}, skipping job")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
# rubocop: enable CodeReuse/ActiveRecord
|
# rubocop: enable CodeReuse/ActiveRecord
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def skip_notification?(note)
|
|
||||||
note.review.present?
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Add total count to GraphQL release data
|
||||||
|
merge_request: 40147
|
||||||
|
author:
|
||||||
|
type: added
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Store deployment_type of Jira server in jira_tracker_data table
|
||||||
|
merge_request: 37003
|
||||||
|
author:
|
||||||
|
type: changed
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Update commit toggle description button to gl-button
|
||||||
|
merge_request: 40524
|
||||||
|
author:
|
||||||
|
type: changed
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Track edit by editor action for Usage Ping
|
||||||
|
merge_request: 40232
|
||||||
|
author:
|
||||||
|
type: changed
|
5
changelogs/unreleased/eb-return-filesize-authorize.yml
Normal file
5
changelogs/unreleased/eb-return-filesize-authorize.yml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Include max artifact size in authorize response
|
||||||
|
merge_request: 37632
|
||||||
|
author:
|
||||||
|
type: added
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Reduce storage requirements for keeping track of pre-logged-in sessions
|
||||||
|
merge_request: 40336
|
||||||
|
author:
|
||||||
|
type: performance
|
|
@ -19,7 +19,7 @@ Rails.application.configure do |config|
|
||||||
|
|
||||||
Warden::Manager.after_authentication(scope: :user) do |user, auth, opts|
|
Warden::Manager.after_authentication(scope: :user) do |user, auth, opts|
|
||||||
ActiveSession.cleanup(user)
|
ActiveSession.cleanup(user)
|
||||||
Gitlab::AnonymousSession.new(auth.request.remote_ip, session_id: auth.request.session.id).cleanup_session_per_ip_entries
|
Gitlab::AnonymousSession.new(auth.request.remote_ip).cleanup_session_per_ip_count
|
||||||
end
|
end
|
||||||
|
|
||||||
Warden::Manager.after_set_user(scope: :user, only: :fetch) do |user, auth, opts|
|
Warden::Manager.after_set_user(scope: :user, only: :fetch) do |user, auth, opts|
|
||||||
|
|
|
@ -12,6 +12,7 @@ class AddIndexOnEndDateAndNamespaceIdToGitlabSubscriptions < ActiveRecord::Migra
|
||||||
end
|
end
|
||||||
|
|
||||||
def down
|
def down
|
||||||
remove_concurrent_index :gitlab_subscriptions, [:end_date, :namespace_id]
|
remove_concurrent_index :gitlab_subscriptions, [:end_date, :namespace_id],
|
||||||
|
name: 'index_gitlab_subscriptions_on_end_date_and_namespace_id'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -22,6 +22,6 @@ class CreateCiPlatformMetrics < ActiveRecord::Migration[6.0]
|
||||||
|
|
||||||
def down
|
def down
|
||||||
drop_table :ci_platform_metrics
|
drop_table :ci_platform_metrics
|
||||||
remove_concurrent_index :ci_variables, :key
|
remove_concurrent_index :ci_variables, :key, name: 'index_ci_variables_on_key'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -13282,6 +13282,11 @@ type ReleaseAssets {
|
||||||
The connection type for Release.
|
The connection type for Release.
|
||||||
"""
|
"""
|
||||||
type ReleaseConnection {
|
type ReleaseConnection {
|
||||||
|
"""
|
||||||
|
Total count of collection
|
||||||
|
"""
|
||||||
|
count: Int!
|
||||||
|
|
||||||
"""
|
"""
|
||||||
A list of edges.
|
A list of edges.
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -38872,6 +38872,24 @@
|
||||||
"name": "ReleaseConnection",
|
"name": "ReleaseConnection",
|
||||||
"description": "The connection type for Release.",
|
"description": "The connection type for Release.",
|
||||||
"fields": [
|
"fields": [
|
||||||
|
{
|
||||||
|
"name": "count",
|
||||||
|
"description": "Total count of collection",
|
||||||
|
"args": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"type": {
|
||||||
|
"kind": "NON_NULL",
|
||||||
|
"name": null,
|
||||||
|
"ofType": {
|
||||||
|
"kind": "SCALAR",
|
||||||
|
"name": "Int",
|
||||||
|
"ofType": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"isDeprecated": false,
|
||||||
|
"deprecationReason": null
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "edges",
|
"name": "edges",
|
||||||
"description": "A list of edges.",
|
"description": "A list of edges.",
|
||||||
|
|
|
@ -350,15 +350,6 @@ administrator can open a Rails console and disable it with the following command
|
||||||
Feature.disable(:cycle_analytics_scatterplot_enabled)
|
Feature.disable(:cycle_analytics_scatterplot_enabled)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Disabling chart median line
|
|
||||||
|
|
||||||
This chart's median line is enabled by default. If you have a self-managed instance, an
|
|
||||||
administrator can open a Rails console and disable it with the following command:
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
Feature.disable(:cycle_analytics_scatterplot_median_enabled)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Type of work - Tasks by type chart
|
## Type of work - Tasks by type chart
|
||||||
|
|
||||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/32421) in GitLab 12.10.
|
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/32421) in GitLab 12.10.
|
||||||
|
|
|
@ -308,7 +308,7 @@ stages:
|
||||||
deploy:
|
deploy:
|
||||||
stage: deploy
|
stage: deploy
|
||||||
script:
|
script:
|
||||||
- echo '//gitlab.com/api/v4/projects/${CI_PROJECT_ID}/packages/npm/:_authToken=${CI_JOB_TOKEN}'>.npmrc
|
- echo "//gitlab.com/api/v4/projects/${CI_PROJECT_ID}/packages/npm/:_authToken=${CI_JOB_TOKEN}">.npmrc
|
||||||
- npm publish
|
- npm publish
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -2,35 +2,34 @@
|
||||||
|
|
||||||
module Gitlab
|
module Gitlab
|
||||||
class AnonymousSession
|
class AnonymousSession
|
||||||
def initialize(remote_ip, session_id: nil)
|
def initialize(remote_ip)
|
||||||
@remote_ip = remote_ip
|
@remote_ip = remote_ip
|
||||||
@session_id = session_id
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def store_session_id_per_ip
|
def count_session_ip
|
||||||
Gitlab::Redis::SharedState.with do |redis|
|
Gitlab::Redis::SharedState.with do |redis|
|
||||||
redis.pipelined do
|
redis.pipelined do
|
||||||
redis.sadd(session_lookup_name, session_id)
|
redis.incr(session_lookup_name)
|
||||||
redis.expire(session_lookup_name, 24.hours)
|
redis.expire(session_lookup_name, 24.hours)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def stored_sessions
|
def session_count
|
||||||
Gitlab::Redis::SharedState.with do |redis|
|
Gitlab::Redis::SharedState.with do |redis|
|
||||||
redis.scard(session_lookup_name)
|
redis.get(session_lookup_name).to_i
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def cleanup_session_per_ip_entries
|
def cleanup_session_per_ip_count
|
||||||
Gitlab::Redis::SharedState.with do |redis|
|
Gitlab::Redis::SharedState.with do |redis|
|
||||||
redis.srem(session_lookup_name, session_id)
|
redis.del(session_lookup_name)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
attr_reader :remote_ip, :session_id
|
attr_reader :remote_ip
|
||||||
|
|
||||||
def session_lookup_name
|
def session_lookup_name
|
||||||
@session_lookup_name ||= "#{Gitlab::Redis::SharedState::IP_SESSIONS_LOOKUP_NAMESPACE}:#{remote_ip}"
|
@session_lookup_name ||= "#{Gitlab::Redis::SharedState::IP_SESSIONS_LOOKUP_NAMESPACE}:#{remote_ip}"
|
||||||
|
|
|
@ -9,7 +9,7 @@ module Gitlab
|
||||||
SESSION_NAMESPACE = 'session:gitlab'
|
SESSION_NAMESPACE = 'session:gitlab'
|
||||||
USER_SESSIONS_NAMESPACE = 'session:user:gitlab'
|
USER_SESSIONS_NAMESPACE = 'session:user:gitlab'
|
||||||
USER_SESSIONS_LOOKUP_NAMESPACE = 'session:lookup:user:gitlab'
|
USER_SESSIONS_LOOKUP_NAMESPACE = 'session:lookup:user:gitlab'
|
||||||
IP_SESSIONS_LOOKUP_NAMESPACE = 'session:lookup:ip:gitlab'
|
IP_SESSIONS_LOOKUP_NAMESPACE = 'session:lookup:ip:gitlab2'
|
||||||
DEFAULT_REDIS_SHARED_STATE_URL = 'redis://localhost:6382'
|
DEFAULT_REDIS_SHARED_STATE_URL = 'redis://localhost:6382'
|
||||||
REDIS_SHARED_STATE_CONFIG_ENV_VAR_NAME = 'GITLAB_REDIS_SHARED_STATE_CONFIG_FILE'
|
REDIS_SHARED_STATE_CONFIG_ENV_VAR_NAME = 'GITLAB_REDIS_SHARED_STATE_CONFIG_FILE'
|
||||||
|
|
||||||
|
|
|
@ -3,9 +3,10 @@
|
||||||
module Gitlab
|
module Gitlab
|
||||||
module UsageDataCounters
|
module UsageDataCounters
|
||||||
module EditorUniqueCounter
|
module EditorUniqueCounter
|
||||||
EDIT_BY_SNIPPET_EDITOR = :edit_by_snippet_editor
|
EDIT_BY_SNIPPET_EDITOR = 'g_edit_by_snippet_ide'
|
||||||
EDIT_BY_SFE = :edit_by_sfe
|
EDIT_BY_SFE = 'g_edit_by_sfe'
|
||||||
EDIT_BY_WEB_IDE = :edit_by_web_ide
|
EDIT_BY_WEB_IDE = 'g_edit_by_web_ide'
|
||||||
|
EDIT_CATEGORY = 'ide_edit'
|
||||||
|
|
||||||
class << self
|
class << self
|
||||||
def track_web_ide_edit_action(author:, time: Time.zone.now)
|
def track_web_ide_edit_action(author:, time: Time.zone.now)
|
||||||
|
@ -32,16 +33,22 @@ module Gitlab
|
||||||
count_unique(EDIT_BY_SNIPPET_EDITOR, date_from, date_to)
|
count_unique(EDIT_BY_SNIPPET_EDITOR, date_from, date_to)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def count_edit_using_editor(date_from:, date_to:)
|
||||||
|
events = Gitlab::UsageDataCounters::HLLRedisCounter.events_for_category(EDIT_CATEGORY)
|
||||||
|
count_unique(events, date_from, date_to)
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def track_unique_action(action, author, time)
|
def track_unique_action(action, author, time)
|
||||||
return unless Feature.enabled?(:track_editor_edit_actions)
|
return unless Feature.enabled?(:track_editor_edit_actions)
|
||||||
|
return unless author
|
||||||
|
|
||||||
Gitlab::UsageDataCounters::TrackUniqueActions.track_action(action: action, author_id: author.id, time: time)
|
Gitlab::UsageDataCounters::HLLRedisCounter.track_event(author.id, action, time)
|
||||||
end
|
end
|
||||||
|
|
||||||
def count_unique(action, date_from, date_to)
|
def count_unique(actions, date_from, date_to)
|
||||||
Gitlab::UsageDataCounters::TrackUniqueActions.count_unique(action: action, date_from: date_from, date_to: date_to)
|
Gitlab::UsageDataCounters::HLLRedisCounter.unique_events(event_names: actions, start_date: date_from, end_date: date_to)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -69,3 +69,18 @@
|
||||||
category: analytics
|
category: analytics
|
||||||
redis_slot: analytics
|
redis_slot: analytics
|
||||||
aggregation: weekly
|
aggregation: weekly
|
||||||
|
- name: g_edit_by_web_ide
|
||||||
|
category: ide_edit
|
||||||
|
redis_slot: edit
|
||||||
|
expiry: 29
|
||||||
|
aggregation: daily
|
||||||
|
- name: g_edit_by_sfe
|
||||||
|
category: ide_edit
|
||||||
|
redis_slot: edit
|
||||||
|
expiry: 29
|
||||||
|
aggregation: daily
|
||||||
|
- name: g_edit_by_snippet_ide
|
||||||
|
category: ide_edit
|
||||||
|
redis_slot: edit
|
||||||
|
expiry: 29
|
||||||
|
aggregation: daily
|
||||||
|
|
|
@ -24992,9 +24992,6 @@ msgstr ""
|
||||||
msgid "There was an error while fetching value stream analytics duration data."
|
msgid "There was an error while fetching value stream analytics duration data."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "There was an error while fetching value stream analytics duration median data."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
|
msgid "There was an error with the reCAPTCHA. Please solve the reCAPTCHA again."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
67
rubocop/cop/migration/refer_to_index_by_name.rb
Normal file
67
rubocop/cop/migration/refer_to_index_by_name.rb
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require_relative '../../migration_helpers'
|
||||||
|
|
||||||
|
module RuboCop
|
||||||
|
module Cop
|
||||||
|
module Migration
|
||||||
|
class ReferToIndexByName < RuboCop::Cop::Cop
|
||||||
|
include MigrationHelpers
|
||||||
|
|
||||||
|
MSG = 'migration methods that refer to existing indexes must do so by name'
|
||||||
|
|
||||||
|
def_node_matcher :match_index_exists, <<~PATTERN
|
||||||
|
(send _ :index_exists? _ _ (hash $...) ?)
|
||||||
|
PATTERN
|
||||||
|
|
||||||
|
def_node_matcher :match_remove_index, <<~PATTERN
|
||||||
|
(send _ :remove_index _ $_)
|
||||||
|
PATTERN
|
||||||
|
|
||||||
|
def_node_matcher :match_remove_concurrent_index, <<~PATTERN
|
||||||
|
(send _ :remove_concurrent_index _ _ (hash $...) ?)
|
||||||
|
PATTERN
|
||||||
|
|
||||||
|
def_node_matcher :name_option?, <<~PATTERN
|
||||||
|
(pair {(sym :name) (str "name")} _)
|
||||||
|
PATTERN
|
||||||
|
|
||||||
|
def on_def(node)
|
||||||
|
return unless in_migration?(node)
|
||||||
|
|
||||||
|
node.each_descendant(:send) do |send_node|
|
||||||
|
next unless index_exists_offense?(send_node) || removing_index_offense?(send_node)
|
||||||
|
|
||||||
|
add_offense(send_node, location: :selector)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def index_exists_offense?(send_node)
|
||||||
|
match_index_exists(send_node) { |option_nodes| needs_name_option?(option_nodes) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def removing_index_offense?(send_node)
|
||||||
|
remove_index_offense?(send_node) || remove_concurrent_index_offense?(send_node)
|
||||||
|
end
|
||||||
|
|
||||||
|
def remove_index_offense?(send_node)
|
||||||
|
match_remove_index(send_node) do |column_or_options_node|
|
||||||
|
break true unless column_or_options_node.type == :hash
|
||||||
|
|
||||||
|
column_or_options_node.children.none? { |pair| name_option?(pair) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def remove_concurrent_index_offense?(send_node)
|
||||||
|
match_remove_concurrent_index(send_node) { |option_nodes| needs_name_option?(option_nodes) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def needs_name_option?(option_nodes)
|
||||||
|
option_nodes.empty? || option_nodes.first.none? { |node| name_option?(node) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -23,9 +23,12 @@ RSpec.describe Admin::IntegrationsController do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#update' do
|
describe '#update' do
|
||||||
|
include JiraServiceHelper
|
||||||
|
|
||||||
let(:integration) { create(:jira_service, :instance) }
|
let(:integration) { create(:jira_service, :instance) }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
|
stub_jira_service_test
|
||||||
allow(PropagateIntegrationWorker).to receive(:perform_async)
|
allow(PropagateIntegrationWorker).to receive(:perform_async)
|
||||||
|
|
||||||
put :update, params: { id: integration.class.to_param, service: { url: url } }
|
put :update, params: { id: integration.class.to_param, service: { url: url } }
|
||||||
|
|
|
@ -81,10 +81,13 @@ RSpec.describe Groups::Settings::IntegrationsController do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#update' do
|
describe '#update' do
|
||||||
|
include JiraServiceHelper
|
||||||
|
|
||||||
let(:integration) { create(:jira_service, project: nil, group_id: group.id) }
|
let(:integration) { create(:jira_service, project: nil, group_id: group.id) }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
group.add_owner(user)
|
group.add_owner(user)
|
||||||
|
stub_jira_service_test
|
||||||
|
|
||||||
put :update, params: { group_id: group, id: integration.class.to_param, service: { url: url } }
|
put :update, params: { group_id: group, id: integration.class.to_param, service: { url: url } }
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
|
|
||||||
RSpec.describe Projects::ServicesController do
|
RSpec.describe Projects::ServicesController do
|
||||||
|
include JiraServiceHelper
|
||||||
|
|
||||||
let(:project) { create(:project, :repository) }
|
let(:project) { create(:project, :repository) }
|
||||||
let(:user) { create(:user) }
|
let(:user) { create(:user) }
|
||||||
let(:service) { create(:jira_service, project: project) }
|
let(:service) { create(:jira_service, project: project) }
|
||||||
|
@ -54,8 +56,7 @@ RSpec.describe Projects::ServicesController do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns success' do
|
it 'returns success' do
|
||||||
stub_request(:get, 'http://example.com/rest/api/2/serverInfo')
|
stub_jira_service_test
|
||||||
.to_return(status: 200, body: '{}')
|
|
||||||
|
|
||||||
expect(Gitlab::HTTP).to receive(:get).with('/rest/api/2/serverInfo', any_args).and_call_original
|
expect(Gitlab::HTTP).to receive(:get).with('/rest/api/2/serverInfo', any_args).and_call_original
|
||||||
|
|
||||||
|
@ -66,8 +67,7 @@ RSpec.describe Projects::ServicesController do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns success' do
|
it 'returns success' do
|
||||||
stub_request(:get, 'http://example.com/rest/api/2/serverInfo')
|
stub_jira_service_test
|
||||||
.to_return(status: 200, body: '{}')
|
|
||||||
|
|
||||||
expect(Gitlab::HTTP).to receive(:get).with('/rest/api/2/serverInfo', any_args).and_call_original
|
expect(Gitlab::HTTP).to receive(:get).with('/rest/api/2/serverInfo', any_args).and_call_original
|
||||||
|
|
||||||
|
@ -200,6 +200,7 @@ RSpec.describe Projects::ServicesController do
|
||||||
|
|
||||||
describe 'as JSON' do
|
describe 'as JSON' do
|
||||||
before do
|
before do
|
||||||
|
stub_jira_service_test
|
||||||
put :update, params: project_params(service: service_params, format: :json)
|
put :update, params: project_params(service: service_params, format: :json)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -229,7 +229,7 @@ RSpec.describe SessionsController do
|
||||||
|
|
||||||
context 'when there are more than 5 anonymous session with the same IP' do
|
context 'when there are more than 5 anonymous session with the same IP' do
|
||||||
before do
|
before do
|
||||||
allow(Gitlab::AnonymousSession).to receive_message_chain(:new, :stored_sessions).and_return(6)
|
allow(Gitlab::AnonymousSession).to receive_message_chain(:new, :session_count).and_return(6)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'displays an error when the reCAPTCHA is not solved' do
|
it 'displays an error when the reCAPTCHA is not solved' do
|
||||||
|
@ -241,7 +241,7 @@ RSpec.describe SessionsController do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'successfully logs in a user when reCAPTCHA is solved' do
|
it 'successfully logs in a user when reCAPTCHA is solved' do
|
||||||
expect(Gitlab::AnonymousSession).to receive_message_chain(:new, :cleanup_session_per_ip_entries)
|
expect(Gitlab::AnonymousSession).to receive_message_chain(:new, :cleanup_session_per_ip_count)
|
||||||
|
|
||||||
succesful_login(user_params)
|
succesful_login(user_params)
|
||||||
|
|
||||||
|
|
|
@ -61,7 +61,10 @@ RSpec.describe 'User activates Jira', :js do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'user disables the Jira Service' do
|
describe 'user disables the Jira Service' do
|
||||||
|
include JiraServiceHelper
|
||||||
|
|
||||||
before do
|
before do
|
||||||
|
stub_jira_service_test
|
||||||
visit_project_integration('Jira')
|
visit_project_integration('Jira')
|
||||||
fill_form(disable: true)
|
fill_form(disable: true)
|
||||||
click_button('Save changes')
|
click_button('Save changes')
|
||||||
|
|
|
@ -233,7 +233,7 @@ describe('AlertDetails', () => {
|
||||||
|
|
||||||
describe('header', () => {
|
describe('header', () => {
|
||||||
const findHeader = () => wrapper.find('[data-testid="alert-header"]');
|
const findHeader = () => wrapper.find('[data-testid="alert-header"]');
|
||||||
const stubs = { TimeAgoTooltip: '<span>now</span>' };
|
const stubs = { TimeAgoTooltip: { template: '<span>now</span>' } };
|
||||||
|
|
||||||
describe('individual header fields', () => {
|
describe('individual header fields', () => {
|
||||||
describe.each`
|
describe.each`
|
||||||
|
|
|
@ -31,7 +31,7 @@ describe('Suggest gitlab-ci.yml Popover', () => {
|
||||||
humanAccess,
|
humanAccess,
|
||||||
},
|
},
|
||||||
stubs: {
|
stubs: {
|
||||||
'gl-popover': '<div><slot name="title"></slot><slot></slot></div>',
|
'gl-popover': { template: '<div><slot name="title"></slot><slot></slot></div>' },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,8 +24,7 @@ describe('diffs/components/commit_item', () => {
|
||||||
|
|
||||||
const getTitleElement = () => wrapper.find('.commit-row-message.item-title');
|
const getTitleElement = () => wrapper.find('.commit-row-message.item-title');
|
||||||
const getDescElement = () => wrapper.find('pre.commit-row-description');
|
const getDescElement = () => wrapper.find('pre.commit-row-description');
|
||||||
const getDescExpandElement = () =>
|
const getDescExpandElement = () => wrapper.find('.commit-content .js-toggle-button');
|
||||||
wrapper.find('.commit-content .text-expander.js-toggle-button');
|
|
||||||
const getShaElement = () => wrapper.find('.commit-sha-group');
|
const getShaElement = () => wrapper.find('.commit-sha-group');
|
||||||
const getAvatarElement = () => wrapper.find('.user-avatar-link');
|
const getAvatarElement = () => wrapper.find('.user-avatar-link');
|
||||||
const getCommitterElement = () => wrapper.find('.committer');
|
const getCommitterElement = () => wrapper.find('.committer');
|
||||||
|
|
|
@ -71,14 +71,14 @@ describe('Embed Group', () => {
|
||||||
|
|
||||||
it('is expanded by default', () => {
|
it('is expanded by default', () => {
|
||||||
metricsWithDataGetter.mockReturnValue([1]);
|
metricsWithDataGetter.mockReturnValue([1]);
|
||||||
mountComponent({ shallow: false, stubs: { MetricEmbed: '<div />' } });
|
mountComponent({ shallow: false, stubs: { MetricEmbed: true } });
|
||||||
|
|
||||||
expect(wrapper.find('.card-body').classes()).not.toContain('d-none');
|
expect(wrapper.find('.card-body').classes()).not.toContain('d-none');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('collapses when clicked', done => {
|
it('collapses when clicked', done => {
|
||||||
metricsWithDataGetter.mockReturnValue([1]);
|
metricsWithDataGetter.mockReturnValue([1]);
|
||||||
mountComponent({ shallow: false, stubs: { MetricEmbed: '<div />' } });
|
mountComponent({ shallow: false, stubs: { MetricEmbed: true } });
|
||||||
|
|
||||||
wrapper.find(GlButton).trigger('click');
|
wrapper.find(GlButton).trigger('click');
|
||||||
|
|
||||||
|
@ -148,14 +148,14 @@ describe('Embed Group', () => {
|
||||||
describe('button text', () => {
|
describe('button text', () => {
|
||||||
it('has a singular label when there is one embed', () => {
|
it('has a singular label when there is one embed', () => {
|
||||||
metricsWithDataGetter.mockReturnValue([1]);
|
metricsWithDataGetter.mockReturnValue([1]);
|
||||||
mountComponent({ shallow: false, stubs: { MetricEmbed: '<div />' } });
|
mountComponent({ shallow: false, stubs: { MetricEmbed: true } });
|
||||||
|
|
||||||
expect(wrapper.find(GlButton).text()).toBe('Hide chart');
|
expect(wrapper.find(GlButton).text()).toBe('Hide chart');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('has a plural label when there are multiple embeds', () => {
|
it('has a plural label when there are multiple embeds', () => {
|
||||||
metricsWithDataGetter.mockReturnValue([2]);
|
metricsWithDataGetter.mockReturnValue([2]);
|
||||||
mountComponent({ shallow: false, stubs: { MetricEmbed: '<div />' } });
|
mountComponent({ shallow: false, stubs: { MetricEmbed: true } });
|
||||||
|
|
||||||
expect(wrapper.find(GlButton).text()).toBe('Hide charts');
|
expect(wrapper.find(GlButton).text()).toBe('Hide charts');
|
||||||
});
|
});
|
||||||
|
|
|
@ -8,45 +8,36 @@ RSpec.describe Gitlab::AnonymousSession, :clean_gitlab_redis_shared_state do
|
||||||
|
|
||||||
subject { new_anonymous_session }
|
subject { new_anonymous_session }
|
||||||
|
|
||||||
def new_anonymous_session(session_id = default_session_id)
|
def new_anonymous_session
|
||||||
described_class.new('127.0.0.1', session_id: session_id)
|
described_class.new('127.0.0.1')
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#store_session_id_per_ip' do
|
describe '#store_session_ip' do
|
||||||
it 'adds session id to proper key' do
|
it 'adds session id to proper key' do
|
||||||
subject.store_session_id_per_ip
|
subject.count_session_ip
|
||||||
|
|
||||||
Gitlab::Redis::SharedState.with do |redis|
|
Gitlab::Redis::SharedState.with do |redis|
|
||||||
expect(redis.smembers("session:lookup:ip:gitlab:127.0.0.1")).to eq [default_session_id]
|
expect(redis.get("session:lookup:ip:gitlab2:127.0.0.1").to_i).to eq 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'adds expiration time to key' do
|
it 'adds expiration time to key' do
|
||||||
Timecop.freeze do
|
Timecop.freeze do
|
||||||
subject.store_session_id_per_ip
|
subject.count_session_ip
|
||||||
|
|
||||||
Gitlab::Redis::SharedState.with do |redis|
|
Gitlab::Redis::SharedState.with do |redis|
|
||||||
expect(redis.ttl("session:lookup:ip:gitlab:127.0.0.1")).to eq(24.hours.to_i)
|
expect(redis.ttl("session:lookup:ip:gitlab2:127.0.0.1")).to eq(24.hours.to_i)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'adds id only once' do
|
|
||||||
subject.store_session_id_per_ip
|
|
||||||
subject.store_session_id_per_ip
|
|
||||||
|
|
||||||
Gitlab::Redis::SharedState.with do |redis|
|
|
||||||
expect(redis.smembers("session:lookup:ip:gitlab:127.0.0.1")).to eq [default_session_id]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when there is already one session' do
|
context 'when there is already one session' do
|
||||||
it 'adds session id to proper key' do
|
it 'increments the session count' do
|
||||||
subject.store_session_id_per_ip
|
subject.count_session_ip
|
||||||
new_anonymous_session(additional_session_id).store_session_id_per_ip
|
new_anonymous_session.count_session_ip
|
||||||
|
|
||||||
Gitlab::Redis::SharedState.with do |redis|
|
Gitlab::Redis::SharedState.with do |redis|
|
||||||
expect(redis.smembers("session:lookup:ip:gitlab:127.0.0.1")).to contain_exactly(default_session_id, additional_session_id)
|
expect(redis.get("session:lookup:ip:gitlab2:127.0.0.1").to_i).to eq(2)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -55,24 +46,22 @@ RSpec.describe Gitlab::AnonymousSession, :clean_gitlab_redis_shared_state do
|
||||||
describe '#stored_sessions' do
|
describe '#stored_sessions' do
|
||||||
it 'returns all anonymous sessions per ip' do
|
it 'returns all anonymous sessions per ip' do
|
||||||
Gitlab::Redis::SharedState.with do |redis|
|
Gitlab::Redis::SharedState.with do |redis|
|
||||||
redis.sadd("session:lookup:ip:gitlab:127.0.0.1", default_session_id)
|
redis.set("session:lookup:ip:gitlab2:127.0.0.1", 2)
|
||||||
redis.sadd("session:lookup:ip:gitlab:127.0.0.1", additional_session_id)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
expect(subject.stored_sessions).to eq(2)
|
expect(subject.session_count).to eq(2)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'removes obsolete lookup through ip entries' do
|
it 'removes obsolete lookup through ip entries' do
|
||||||
Gitlab::Redis::SharedState.with do |redis|
|
Gitlab::Redis::SharedState.with do |redis|
|
||||||
redis.sadd("session:lookup:ip:gitlab:127.0.0.1", default_session_id)
|
redis.set("session:lookup:ip:gitlab2:127.0.0.1", 2)
|
||||||
redis.sadd("session:lookup:ip:gitlab:127.0.0.1", additional_session_id)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
subject.cleanup_session_per_ip_entries
|
subject.cleanup_session_per_ip_count
|
||||||
|
|
||||||
Gitlab::Redis::SharedState.with do |redis|
|
Gitlab::Redis::SharedState.with do |redis|
|
||||||
expect(redis.smembers("session:lookup:ip:gitlab:127.0.0.1")).to eq [additional_session_id]
|
expect(redis.exists("session:lookup:ip:gitlab2:127.0.0.1")).to eq(false)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,15 +3,17 @@
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
|
|
||||||
RSpec.describe Gitlab::UsageDataCounters::EditorUniqueCounter, :clean_gitlab_redis_shared_state do
|
RSpec.describe Gitlab::UsageDataCounters::EditorUniqueCounter, :clean_gitlab_redis_shared_state do
|
||||||
|
let(:user1) { build(:user, id: 1) }
|
||||||
|
let(:user2) { build(:user, id: 2) }
|
||||||
|
let(:user3) { build(:user, id: 3) }
|
||||||
|
let(:time) { Time.zone.now }
|
||||||
|
|
||||||
shared_examples 'tracks and counts action' do
|
shared_examples 'tracks and counts action' do
|
||||||
let(:user1) { build(:user, id: 1) }
|
before do
|
||||||
let(:user2) { build(:user, id: 2) }
|
stub_application_setting(usage_ping_enabled: true)
|
||||||
let(:user3) { build(:user, id: 3) }
|
end
|
||||||
let(:time) { Time.zone.now }
|
|
||||||
|
|
||||||
specify do
|
specify do
|
||||||
stub_application_setting(usage_ping_enabled: true)
|
|
||||||
|
|
||||||
aggregate_failures do
|
aggregate_failures do
|
||||||
expect(track_action(author: user1)).to be_truthy
|
expect(track_action(author: user1)).to be_truthy
|
||||||
expect(track_action(author: user1)).to be_truthy
|
expect(track_action(author: user1)).to be_truthy
|
||||||
|
@ -23,6 +25,10 @@ RSpec.describe Gitlab::UsageDataCounters::EditorUniqueCounter, :clean_gitlab_red
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'does not track edit actions if author is not present' do
|
||||||
|
expect(track_action(author: nil)).to be_nil
|
||||||
|
end
|
||||||
|
|
||||||
context 'when feature flag track_editor_edit_actions is disabled' do
|
context 'when feature flag track_editor_edit_actions is disabled' do
|
||||||
it 'does not track edit actions' do
|
it 'does not track edit actions' do
|
||||||
stub_feature_flags(track_editor_edit_actions: false)
|
stub_feature_flags(track_editor_edit_actions: false)
|
||||||
|
@ -67,4 +73,17 @@ RSpec.describe Gitlab::UsageDataCounters::EditorUniqueCounter, :clean_gitlab_red
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'can return the count of actions per user deduplicated ' do
|
||||||
|
described_class.track_web_ide_edit_action(author: user1)
|
||||||
|
described_class.track_snippet_editor_edit_action(author: user1)
|
||||||
|
described_class.track_sfe_edit_action(author: user1)
|
||||||
|
described_class.track_web_ide_edit_action(author: user2, time: time - 2.days)
|
||||||
|
described_class.track_web_ide_edit_action(author: user3, time: time - 3.days)
|
||||||
|
described_class.track_snippet_editor_edit_action(author: user3, time: time - 3.days)
|
||||||
|
described_class.track_sfe_edit_action(author: user3, time: time - 3.days)
|
||||||
|
|
||||||
|
expect(described_class.count_edit_using_editor(date_from: time, date_to: Date.today)).to eq(1)
|
||||||
|
expect(described_class.count_edit_using_editor(date_from: time - 5.days, date_to: Date.tomorrow)).to eq(3)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1416,4 +1416,20 @@ RSpec.describe Note do
|
||||||
expect(note.parent_user).to be_nil
|
expect(note.parent_user).to be_nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '#skip_notification?' do
|
||||||
|
subject(:skip_notification?) { note.skip_notification? }
|
||||||
|
|
||||||
|
context 'when there is no review' do
|
||||||
|
let(:note) { build(:note) }
|
||||||
|
|
||||||
|
it { is_expected.to be_falsey }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the review exists' do
|
||||||
|
let(:note) { build(:note, :with_review) }
|
||||||
|
|
||||||
|
it { is_expected.to be_truthy }
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -10,6 +10,11 @@ RSpec.describe JiraService do
|
||||||
let(:username) { 'jira-username' }
|
let(:username) { 'jira-username' }
|
||||||
let(:password) { 'jira-password' }
|
let(:password) { 'jira-password' }
|
||||||
let(:transition_id) { 'test27' }
|
let(:transition_id) { 'test27' }
|
||||||
|
let(:server_info_results) { { 'deploymentType' => 'Cloud' } }
|
||||||
|
|
||||||
|
before do
|
||||||
|
WebMock.stub_request(:get, /serverInfo/).to_return(body: server_info_results.to_json )
|
||||||
|
end
|
||||||
|
|
||||||
describe '#options' do
|
describe '#options' do
|
||||||
let(:options) do
|
let(:options) do
|
||||||
|
@ -103,7 +108,7 @@ RSpec.describe JiraService do
|
||||||
expect(subject.properties).to be_nil
|
expect(subject.properties).to be_nil
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'stores data in data_fields correcty' do
|
it 'stores data in data_fields correctly' do
|
||||||
service = subject
|
service = subject
|
||||||
|
|
||||||
expect(service.jira_tracker_data.url).to eq(url)
|
expect(service.jira_tracker_data.url).to eq(url)
|
||||||
|
@ -111,6 +116,35 @@ RSpec.describe JiraService do
|
||||||
expect(service.jira_tracker_data.username).to eq(username)
|
expect(service.jira_tracker_data.username).to eq(username)
|
||||||
expect(service.jira_tracker_data.password).to eq(password)
|
expect(service.jira_tracker_data.password).to eq(password)
|
||||||
expect(service.jira_tracker_data.jira_issue_transition_id).to eq(transition_id)
|
expect(service.jira_tracker_data.jira_issue_transition_id).to eq(transition_id)
|
||||||
|
expect(service.jira_tracker_data.deployment_cloud?).to be_truthy
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when loading serverInfo' do
|
||||||
|
let!(:jira_service) { subject }
|
||||||
|
|
||||||
|
context 'Cloud instance' do
|
||||||
|
let(:server_info_results) { { 'deploymentType' => 'Cloud' } }
|
||||||
|
|
||||||
|
it 'is detected' do
|
||||||
|
expect(jira_service.jira_tracker_data.deployment_cloud?).to be_truthy
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'Server instance' do
|
||||||
|
let(:server_info_results) { { 'deploymentType' => 'Server' } }
|
||||||
|
|
||||||
|
it 'is detected' do
|
||||||
|
expect(jira_service.jira_tracker_data.deployment_server?).to be_truthy
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'Unknown instance' do
|
||||||
|
let(:server_info_results) { { 'deploymentType' => 'FutureCloud' } }
|
||||||
|
|
||||||
|
it 'is detected' do
|
||||||
|
expect(jira_service.jira_tracker_data.deployment_unknown?).to be_truthy
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -151,8 +185,8 @@ RSpec.describe JiraService do
|
||||||
|
|
||||||
describe '#update' do
|
describe '#update' do
|
||||||
context 'basic update' do
|
context 'basic update' do
|
||||||
let(:new_username) { 'new_username' }
|
let_it_be(:new_username) { 'new_username' }
|
||||||
let(:new_url) { 'http://jira-new.example.com' }
|
let_it_be(:new_url) { 'http://jira-new.example.com' }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
service.update(username: new_username, url: new_url)
|
service.update(username: new_username, url: new_url)
|
||||||
|
@ -173,6 +207,53 @@ RSpec.describe JiraService do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when updating the url, api_url, username, or password' do
|
||||||
|
it 'updates deployment type' do
|
||||||
|
service.update(url: 'http://first.url')
|
||||||
|
service.jira_tracker_data.update(deployment_type: 'server')
|
||||||
|
|
||||||
|
expect(service.jira_tracker_data.deployment_server?).to be_truthy
|
||||||
|
|
||||||
|
service.update(api_url: 'http://another.url')
|
||||||
|
service.jira_tracker_data.reload
|
||||||
|
|
||||||
|
expect(service.jira_tracker_data.deployment_cloud?).to be_truthy
|
||||||
|
expect(WebMock).to have_requested(:get, /serverInfo/).twice
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'calls serverInfo for url' do
|
||||||
|
service.update(url: 'http://first.url')
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:get, /serverInfo/)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'calls serverInfo for api_url' do
|
||||||
|
service.update(api_url: 'http://another.url')
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:get, /serverInfo/)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'calls serverInfo for username' do
|
||||||
|
service.update(username: 'test-user')
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:get, /serverInfo/)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'calls serverInfo for password' do
|
||||||
|
service.update(password: 'test-password')
|
||||||
|
|
||||||
|
expect(WebMock).to have_requested(:get, /serverInfo/)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when not updating the url, api_url, username, or password' do
|
||||||
|
it 'does not update deployment type' do
|
||||||
|
service.update(jira_issue_transition_id: 'jira_issue_transition_id')
|
||||||
|
|
||||||
|
expect(WebMock).not_to have_requested(:get, /serverInfo/)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'stored password invalidation' do
|
context 'stored password invalidation' do
|
||||||
context 'when a password was previously set' do
|
context 'when a password was previously set' do
|
||||||
context 'when only web url present' do
|
context 'when only web url present' do
|
||||||
|
@ -627,6 +708,7 @@ RSpec.describe JiraService do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#test' do
|
describe '#test' do
|
||||||
|
let(:server_info_results) { { 'url' => 'http://url', 'deploymentType' => 'Cloud' } }
|
||||||
let(:jira_service) do
|
let(:jira_service) do
|
||||||
described_class.new(
|
described_class.new(
|
||||||
url: url,
|
url: url,
|
||||||
|
@ -635,24 +717,21 @@ RSpec.describe JiraService do
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_settings(url = 'jira.example.com')
|
def server_info
|
||||||
test_url = "http://#{url}/rest/api/2/serverInfo"
|
|
||||||
|
|
||||||
WebMock.stub_request(:get, test_url).with(basic_auth: [username, password])
|
|
||||||
.to_return(body: { url: 'http://url' }.to_json )
|
|
||||||
|
|
||||||
jira_service.test(nil)
|
jira_service.test(nil)
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when the test succeeds' do
|
context 'when the test succeeds' do
|
||||||
it 'gets Jira project with URL when API URL not set' do
|
it 'gets Jira project with URL when API URL not set' do
|
||||||
expect(test_settings).to eq(success: true, result: { 'url' => 'http://url' })
|
expect(server_info).to eq(success: true, result: server_info_results)
|
||||||
|
expect(WebMock).to have_requested(:get, /jira.example.com/)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'gets Jira project with API URL if set' do
|
it 'gets Jira project with API URL if set' do
|
||||||
jira_service.update(api_url: 'http://jira.api.com')
|
jira_service.update(api_url: 'http://jira.api.com')
|
||||||
|
|
||||||
expect(test_settings('jira.api.com')).to eq(success: true, result: { 'url' => 'http://url' })
|
expect(server_info).to eq(success: true, result: server_info_results)
|
||||||
|
expect(WebMock).to have_requested(:get, /jira.api.com/)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -143,6 +143,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
|
||||||
expect(response.media_type).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
|
expect(response.media_type).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
|
||||||
expect(json_response['TempPath']).to eq(JobArtifactUploader.workhorse_local_upload_path)
|
expect(json_response['TempPath']).to eq(JobArtifactUploader.workhorse_local_upload_path)
|
||||||
expect(json_response['RemoteObject']).to be_nil
|
expect(json_response['RemoteObject']).to be_nil
|
||||||
|
expect(json_response['MaximumSize']).not_to be_nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -167,6 +168,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
|
||||||
expect(json_response['RemoteObject']).to have_key('StoreURL')
|
expect(json_response['RemoteObject']).to have_key('StoreURL')
|
||||||
expect(json_response['RemoteObject']).to have_key('DeleteURL')
|
expect(json_response['RemoteObject']).to have_key('DeleteURL')
|
||||||
expect(json_response['RemoteObject']).to have_key('MultipartUpload')
|
expect(json_response['RemoteObject']).to have_key('MultipartUpload')
|
||||||
|
expect(json_response['MaximumSize']).not_to be_nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -188,6 +190,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
|
||||||
expect(response).to have_gitlab_http_status(:ok)
|
expect(response).to have_gitlab_http_status(:ok)
|
||||||
expect(response.media_type).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
|
expect(response.media_type).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
|
||||||
expect(json_response['TempPath']).not_to be_nil
|
expect(json_response['TempPath']).not_to be_nil
|
||||||
|
expect(json_response['MaximumSize']).not_to be_nil
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'fails to post too large artifact' do
|
it 'fails to post too large artifact' do
|
||||||
|
|
|
@ -14,6 +14,7 @@ RSpec.describe 'Query.project(fullPath).releases()' do
|
||||||
graphql_query_for(:project, { fullPath: project.full_path },
|
graphql_query_for(:project, { fullPath: project.full_path },
|
||||||
%{
|
%{
|
||||||
releases {
|
releases {
|
||||||
|
count
|
||||||
nodes {
|
nodes {
|
||||||
tagName
|
tagName
|
||||||
tagPath
|
tagPath
|
||||||
|
@ -53,6 +54,20 @@ RSpec.describe 'Query.project(fullPath).releases()' do
|
||||||
stub_default_url_options(host: 'www.example.com')
|
stub_default_url_options(host: 'www.example.com')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
shared_examples 'correct total count' do
|
||||||
|
let(:data) { graphql_data.dig('project', 'releases') }
|
||||||
|
|
||||||
|
before do
|
||||||
|
create_list(:release, 2, project: project)
|
||||||
|
|
||||||
|
post_query
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns the total count' do
|
||||||
|
expect(data['count']).to eq(project.releases.count)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
shared_examples 'full access to all repository-related fields' do
|
shared_examples 'full access to all repository-related fields' do
|
||||||
describe 'repository-related fields' do
|
describe 'repository-related fields' do
|
||||||
before do
|
before do
|
||||||
|
@ -92,6 +107,8 @@ RSpec.describe 'Query.project(fullPath).releases()' do
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'correct total count'
|
||||||
end
|
end
|
||||||
|
|
||||||
shared_examples 'no access to any repository-related fields' do
|
shared_examples 'no access to any repository-related fields' do
|
||||||
|
@ -119,6 +136,8 @@ RSpec.describe 'Query.project(fullPath).releases()' do
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'correct total count'
|
||||||
end
|
end
|
||||||
|
|
||||||
# editUrl is tested separately becuase its permissions
|
# editUrl is tested separately becuase its permissions
|
||||||
|
|
90
spec/rubocop/cop/migration/refer_to_index_by_name_spec.rb
Normal file
90
spec/rubocop/cop/migration/refer_to_index_by_name_spec.rb
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
#
|
||||||
|
require 'fast_spec_helper'
|
||||||
|
require 'rubocop'
|
||||||
|
require_relative '../../../../rubocop/cop/migration/refer_to_index_by_name'
|
||||||
|
|
||||||
|
RSpec.describe RuboCop::Cop::Migration::ReferToIndexByName, type: :rubocop do
|
||||||
|
include CopHelper
|
||||||
|
|
||||||
|
subject(:cop) { described_class.new }
|
||||||
|
|
||||||
|
context 'in migration' do
|
||||||
|
before do
|
||||||
|
allow(cop).to receive(:in_migration?).and_return(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when existing indexes are referred to without an explicit name' do
|
||||||
|
it 'registers an offense' do
|
||||||
|
expect_offense(<<~RUBY)
|
||||||
|
class TestReferToIndexByName < ActiveRecord::Migration[6.0]
|
||||||
|
DOWNTIME = false
|
||||||
|
|
||||||
|
INDEX_NAME = 'my_test_name'
|
||||||
|
|
||||||
|
disable_ddl_transaction!
|
||||||
|
|
||||||
|
def up
|
||||||
|
if index_exists? :test_indexes, :column1, name: 'index_name_1'
|
||||||
|
remove_index :test_indexes, column: :column1, name: 'index_name_1'
|
||||||
|
end
|
||||||
|
|
||||||
|
if index_exists? :test_indexes, :column2
|
||||||
|
^^^^^^^^^^^^^ #{described_class::MSG}
|
||||||
|
remove_index :test_indexes, :column2
|
||||||
|
^^^^^^^^^^^^ #{described_class::MSG}
|
||||||
|
end
|
||||||
|
|
||||||
|
remove_index :test_indexes, column: column3
|
||||||
|
^^^^^^^^^^^^ #{described_class::MSG}
|
||||||
|
|
||||||
|
remove_index :test_indexes, name: 'index_name_4'
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
if index_exists? :test_indexes, :column4, using: :gin, opclass: :gin_trgm_ops
|
||||||
|
^^^^^^^^^^^^^ #{described_class::MSG}
|
||||||
|
remove_concurrent_index :test_indexes, :column4, using: :gin, opclass: :gin_trgm_ops
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^ #{described_class::MSG}
|
||||||
|
end
|
||||||
|
|
||||||
|
if index_exists? :test_indexes, :column3, unique: true, name: 'index_name_3', where: 'column3 = 10'
|
||||||
|
remove_concurrent_index :test_indexes, :column3, unique: true, name: 'index_name_3', where: 'column3 = 10'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
RUBY
|
||||||
|
|
||||||
|
expect(cop.offenses.map(&:cop_name)).to all(eq("Migration/#{described_class.name.demodulize}"))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'outside migration' do
|
||||||
|
before do
|
||||||
|
allow(cop).to receive(:in_migration?).and_return(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'registers no offenses' do
|
||||||
|
expect_no_offenses(<<~RUBY)
|
||||||
|
class TestReferToIndexByName < ActiveRecord::Migration[6.0]
|
||||||
|
DOWNTIME = false
|
||||||
|
|
||||||
|
disable_ddl_transaction!
|
||||||
|
|
||||||
|
def up
|
||||||
|
if index_exists? :test_indexes, :column1
|
||||||
|
remove_index :test_indexes, :column1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
if index_exists? :test_indexes, :column1
|
||||||
|
remove_concurrent_index :test_indexes, :column1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
RUBY
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -4,6 +4,12 @@ require 'spec_helper'
|
||||||
|
|
||||||
RSpec.describe Admin::PropagateIntegrationService do
|
RSpec.describe Admin::PropagateIntegrationService do
|
||||||
describe '.propagate' do
|
describe '.propagate' do
|
||||||
|
include JiraServiceHelper
|
||||||
|
|
||||||
|
before do
|
||||||
|
stub_jira_service_test
|
||||||
|
end
|
||||||
|
|
||||||
let(:excluded_attributes) { %w[id project_id inherit_from_id instance created_at updated_at default] }
|
let(:excluded_attributes) { %w[id project_id inherit_from_id instance created_at updated_at default] }
|
||||||
let!(:project) { create(:project) }
|
let!(:project) { create(:project) }
|
||||||
let!(:instance_integration) do
|
let!(:instance_integration) do
|
||||||
|
|
|
@ -416,6 +416,7 @@ RSpec.describe Git::BranchPushService, services: true do
|
||||||
before do
|
before do
|
||||||
# project.create_jira_service doesn't seem to invalidate the cache here
|
# project.create_jira_service doesn't seem to invalidate the cache here
|
||||||
project.has_external_issue_tracker = true
|
project.has_external_issue_tracker = true
|
||||||
|
stub_jira_service_test
|
||||||
jira_service_settings
|
jira_service_settings
|
||||||
stub_jira_urls("JIRA-1")
|
stub_jira_urls("JIRA-1")
|
||||||
|
|
||||||
|
|
|
@ -152,6 +152,7 @@ RSpec.describe MergeRequests::MergeService do
|
||||||
let(:commit) { double('commit', safe_message: "Fixes #{jira_issue.to_reference}") }
|
let(:commit) { double('commit', safe_message: "Fixes #{jira_issue.to_reference}") }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
|
stub_jira_service_test
|
||||||
project.update!(has_external_issue_tracker: true)
|
project.update!(has_external_issue_tracker: true)
|
||||||
jira_service_settings
|
jira_service_settings
|
||||||
stub_jira_urls(jira_issue.id)
|
stub_jira_urls(jira_issue.id)
|
||||||
|
|
|
@ -79,7 +79,11 @@ RSpec.describe Projects::PropagateServiceTemplate do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'service with data fields' do
|
context 'service with data fields' do
|
||||||
|
include JiraServiceHelper
|
||||||
|
|
||||||
let(:service_template) do
|
let(:service_template) do
|
||||||
|
stub_jira_service_test
|
||||||
|
|
||||||
JiraService.create!(
|
JiraService.create!(
|
||||||
template: true,
|
template: true,
|
||||||
active: true,
|
active: true,
|
||||||
|
|
|
@ -347,6 +347,7 @@ RSpec.describe SystemNoteService do
|
||||||
let(:success_message) { "SUCCESS: Successfully posted to http://jira.example.net." }
|
let(:success_message) { "SUCCESS: Successfully posted to http://jira.example.net." }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
|
stub_jira_service_test
|
||||||
stub_jira_urls(jira_issue.id)
|
stub_jira_urls(jira_issue.id)
|
||||||
jira_service_settings
|
jira_service_settings
|
||||||
end
|
end
|
||||||
|
|
|
@ -78,8 +78,7 @@ module JiraServiceHelper
|
||||||
end
|
end
|
||||||
|
|
||||||
def stub_jira_service_test
|
def stub_jira_service_test
|
||||||
WebMock.stub_request(:get, 'https://jira.example.com/rest/api/2/serverInfo')
|
WebMock.stub_request(:get, /serverInfo/).to_return(body: { url: 'http://url' }.to_json)
|
||||||
.to_return(body: { url: 'http://url' }.to_json)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def stub_jira_urls(issue_id)
|
def stub_jira_urls(issue_id)
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
Service.available_services_names.each do |service|
|
Service.available_services_names.each do |service|
|
||||||
RSpec.shared_context service do
|
RSpec.shared_context service do
|
||||||
|
include JiraServiceHelper if service == 'jira'
|
||||||
|
|
||||||
let(:dashed_service) { service.dasherize }
|
let(:dashed_service) { service.dasherize }
|
||||||
let(:service_method) { "#{service}_service".to_sym }
|
let(:service_method) { "#{service}_service".to_sym }
|
||||||
let(:service_klass) { "#{service}_service".classify.constantize }
|
let(:service_klass) { "#{service}_service".classify.constantize }
|
||||||
|
@ -39,6 +41,7 @@ Service.available_services_names.each do |service|
|
||||||
|
|
||||||
before do
|
before do
|
||||||
enable_license_for_service(service)
|
enable_license_for_service(service)
|
||||||
|
stub_jira_service_test if service == 'jira'
|
||||||
end
|
end
|
||||||
|
|
||||||
def initialize_service(service)
|
def initialize_service(service)
|
||||||
|
|
|
@ -414,28 +414,38 @@ RSpec.describe ObjectStorage do
|
||||||
|
|
||||||
subject { uploader_class.workhorse_authorize(has_length: has_length, maximum_size: maximum_size) }
|
subject { uploader_class.workhorse_authorize(has_length: has_length, maximum_size: maximum_size) }
|
||||||
|
|
||||||
shared_examples 'uses local storage' do
|
shared_examples 'returns the maximum size given' do
|
||||||
it "returns temporary path" do
|
it "returns temporary path" do
|
||||||
is_expected.to have_key(:TempPath)
|
expect(subject[:MaximumSize]).to eq(maximum_size)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
expect(subject[:TempPath]).to start_with(uploader_class.root)
|
shared_examples 'uses local storage' do
|
||||||
expect(subject[:TempPath]).to include(described_class::TMP_UPLOAD_PATH)
|
it_behaves_like 'returns the maximum size given' do
|
||||||
|
it "returns temporary path" do
|
||||||
|
is_expected.to have_key(:TempPath)
|
||||||
|
|
||||||
|
expect(subject[:TempPath]).to start_with(uploader_class.root)
|
||||||
|
expect(subject[:TempPath]).to include(described_class::TMP_UPLOAD_PATH)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
shared_examples 'uses remote storage' do
|
shared_examples 'uses remote storage' do
|
||||||
it "returns remote store" do
|
it_behaves_like 'returns the maximum size given' do
|
||||||
is_expected.to have_key(:RemoteObject)
|
it "returns remote store" do
|
||||||
|
is_expected.to have_key(:RemoteObject)
|
||||||
|
|
||||||
expect(subject[:RemoteObject]).to have_key(:ID)
|
expect(subject[:RemoteObject]).to have_key(:ID)
|
||||||
expect(subject[:RemoteObject]).to include(Timeout: a_kind_of(Integer))
|
expect(subject[:RemoteObject]).to include(Timeout: a_kind_of(Integer))
|
||||||
expect(subject[:RemoteObject][:Timeout]).to be(ObjectStorage::DirectUpload::TIMEOUT)
|
expect(subject[:RemoteObject][:Timeout]).to be(ObjectStorage::DirectUpload::TIMEOUT)
|
||||||
expect(subject[:RemoteObject]).to have_key(:GetURL)
|
expect(subject[:RemoteObject]).to have_key(:GetURL)
|
||||||
expect(subject[:RemoteObject]).to have_key(:DeleteURL)
|
expect(subject[:RemoteObject]).to have_key(:DeleteURL)
|
||||||
expect(subject[:RemoteObject]).to have_key(:StoreURL)
|
expect(subject[:RemoteObject]).to have_key(:StoreURL)
|
||||||
expect(subject[:RemoteObject][:GetURL]).to include(described_class::TMP_UPLOAD_PATH)
|
expect(subject[:RemoteObject][:GetURL]).to include(described_class::TMP_UPLOAD_PATH)
|
||||||
expect(subject[:RemoteObject][:DeleteURL]).to include(described_class::TMP_UPLOAD_PATH)
|
expect(subject[:RemoteObject][:DeleteURL]).to include(described_class::TMP_UPLOAD_PATH)
|
||||||
expect(subject[:RemoteObject][:StoreURL]).to include(described_class::TMP_UPLOAD_PATH)
|
expect(subject[:RemoteObject][:StoreURL]).to include(described_class::TMP_UPLOAD_PATH)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -50,10 +50,20 @@ RSpec.describe NewNoteWorker do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when note is with review' do
|
context 'when note does not require notification' do
|
||||||
it 'does not create a new note notification' do
|
let(:note) { create(:note) }
|
||||||
note = create(:note, :with_review)
|
|
||||||
|
|
||||||
|
before do
|
||||||
|
# TODO: `allow_next_instance_of` helper method is not working
|
||||||
|
# because ActiveRecord is directly calling `.allocate` on model
|
||||||
|
# classes and bypasses the `.new` method call.
|
||||||
|
# Fix the `allow_next_instance_of` helper and change these to mock
|
||||||
|
# the next instance of `Note` model class.
|
||||||
|
allow(Note).to receive(:find_by).with(id: note.id).and_return(note)
|
||||||
|
allow(note).to receive(:skip_notification?).and_return(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not create a new note notification' do
|
||||||
expect_any_instance_of(NotificationService).not_to receive(:new_note)
|
expect_any_instance_of(NotificationService).not_to receive(:new_note)
|
||||||
|
|
||||||
subject.perform(note.id)
|
subject.perform(note.id)
|
||||||
|
|
Loading…
Reference in a new issue