Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-07-20 12:09:34 +00:00
parent a7608a4940
commit c780abc85f
39 changed files with 393 additions and 91 deletions

View File

@ -1 +1 @@
9e6f5f40e6eb44655b6acfd5dc222af04333a4f2
521bb978da8780aca690136e78a3ad388726c8ad

View File

@ -440,7 +440,7 @@ gem 'activerecord-explain-analyze', '~> 0.1', require: false
gem 'oauth2', '~> 1.4'
# Health check
gem 'health_check', '~> 2.6.0'
gem 'health_check', '~> 3.0'
# System information
gem 'vmstat', '~> 2.3.0'

View File

@ -518,8 +518,8 @@ GEM
hashie (3.6.0)
hashie-forbidden_attributes (0.1.1)
hashie (>= 3.0)
health_check (2.6.0)
rails (>= 4.0)
health_check (3.0.0)
railties (>= 5.0)
heapy (0.1.4)
hipchat (1.5.2)
httparty
@ -1283,7 +1283,7 @@ DEPENDENCIES
hamlit (~> 2.11.0)
hangouts-chat (~> 0.0.5)
hashie-forbidden_attributes
health_check (~> 2.6.0)
health_check (~> 3.0)
hipchat (~> 1.5.0)
html-pipeline (~> 2.12)
html2text

View File

@ -3,6 +3,7 @@ import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar
import limitWarning from './limit_warning_component.vue';
import totalTime from './total_time_component.vue';
import icon from '../../vue_shared/components/icon.vue';
import { GlIcon } from '@gitlab/ui';
export default {
components: {
@ -10,6 +11,7 @@ export default {
totalTime,
limitWarning,
icon,
GlIcon,
},
props: {
items: {
@ -52,7 +54,8 @@ export default {
</span>
<template v-if="mergeRequest.state === 'closed'">
<span class="merge-request-state">
<i class="fa fa-ban" aria-hidden="true"> </i> {{ mergeRequest.state.toUpperCase() }}
<gl-icon name="cancel" class="gl-vertical-align-text-bottom" />
{{ __('CLOSED') }}
</span>
</template>
<template v-else>

View File

@ -5,7 +5,6 @@ const PERSISTENT_USER_CALLOUTS = [
'.js-users-over-license-callout',
'.js-admin-licensed-user-count-threshold',
'.js-buy-pipeline-minutes-notification-callout',
'.js-alerts-moved-alert',
'.js-token-expiry-callout',
];

View File

@ -9,7 +9,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController
before_action :authorize_read_group!, only: :index
before_action :find_todos, only: [:index, :destroy_all]
track_unique_visits :index, target_id: 'u_analytics_todos'
track_unique_visits :index, target_id: 'u_todos'
def index
@sort = params[:sort]

View File

@ -8,6 +8,7 @@ class Event < ApplicationRecord
include CreatedAtFilterable
include Gitlab::Utils::StrongMemoize
include UsageStatistics
include ShaAttribute
default_scope { reorder(nil) } # rubocop:disable Cop/DefaultScope
@ -48,6 +49,8 @@ class Event < ApplicationRecord
RESET_PROJECT_ACTIVITY_INTERVAL = 1.hour
REPOSITORY_UPDATED_AT_INTERVAL = 5.minutes
sha_attribute :fingerprint
enum action: ACTIONS, _suffix: true
delegate :name, :email, :public_email, :username, to: :author, prefix: true, allow_nil: true
@ -82,6 +85,10 @@ class Event < ApplicationRecord
scope :recent, -> { reorder(id: :desc) }
scope :for_wiki_page, -> { where(target_type: 'WikiPage::Meta') }
scope :for_design, -> { where(target_type: 'DesignManagement::Design') }
scope :for_fingerprint, ->(fingerprint) do
fingerprint.present? ? where(fingerprint: fingerprint) : none
end
scope :for_action, ->(action) { where(action: action) }
scope :with_associations, -> do
# We're using preload for "push_event_payload" as otherwise the association

View File

@ -98,6 +98,7 @@ class WikiPage
def slug
attributes[:slug].presence || wiki.wiki.preview_slug(title, format)
end
alias_method :id, :slug # required to use build_stubbed
alias_method :to_param, :slug
@ -265,8 +266,8 @@ class WikiPage
'../shared/wikis/wiki_page'
end
def id
page.version.to_s
def sha
page.version&.sha
end
def title_changed?

View File

@ -100,25 +100,21 @@ class EventCreateService
# @param [WikiPage::Meta] wiki_page_meta The event target
# @param [User] author The event author
# @param [Symbol] action One of the Event::WIKI_ACTIONS
# @param [String] fingerprint The de-duplication fingerprint
#
# @return a tuple of event and either :found or :created
def wiki_event(wiki_page_meta, author, action)
# The fingerprint, if provided, should be sufficient to find duplicate events.
# Suitable values would be, for example, the current page SHA.
#
# @return [Event] the event
def wiki_event(wiki_page_meta, author, action, fingerprint)
raise IllegalActionError, action unless Event::WIKI_ACTIONS.include?(action)
if duplicate = existing_wiki_event(wiki_page_meta, action)
return duplicate
end
event = create_record_event(wiki_page_meta, author, action)
# Ensure that the event is linked in time to the metadata, for non-deletes
unless event.destroyed_action?
time_stamp = wiki_page_meta.updated_at
event.update_columns(updated_at: time_stamp, created_at: time_stamp)
end
Gitlab::UsageDataCounters::TrackUniqueActions.track_action(event_action: action, event_target: wiki_page_meta.class, author_id: author.id)
event
duplicate = Event.for_wiki_meta(wiki_page_meta).for_fingerprint(fingerprint).first
return duplicate if duplicate.present?
create_record_event(wiki_page_meta, author, action, fingerprint.presence)
end
def approve_mr(merge_request, current_user)
@ -127,44 +123,37 @@ class EventCreateService
private
def existing_wiki_event(wiki_page_meta, action)
if Event.actions.fetch(action) == Event.actions[:destroyed]
most_recent = Event.for_wiki_meta(wiki_page_meta).recent.first
return most_recent if most_recent.present? && Event.actions[most_recent.action] == Event.actions[action]
else
Event.for_wiki_meta(wiki_page_meta).created_at(wiki_page_meta.updated_at).first
end
end
def create_record_event(record, current_user, status)
def create_record_event(record, current_user, status, fingerprint = nil)
create_event(record.resource_parent, current_user, status,
target_id: record.id, target_type: record.class.name)
fingerprint: fingerprint,
target_id: record.id,
target_type: record.class.name)
end
# If creating several events, this method will insert them all in a single
# statement
#
# @param [[Eventable, Symbol]] a list of pairs of records and a valid status
# @param [[Eventable, Symbol, String]] a list of tuples of records, a valid status, and fingerprint
# @param [User] the author of the event
def create_record_events(pairs, current_user)
def create_record_events(tuples, current_user)
base_attrs = {
created_at: Time.now.utc,
updated_at: Time.now.utc,
author_id: current_user.id
}
attribute_sets = pairs.map do |record, status|
attribute_sets = tuples.map do |record, status, fingerprint|
action = Event.actions[status]
raise IllegalActionError, "#{status} is not a valid status" if action.nil?
parent_attrs(record.resource_parent)
.merge(base_attrs)
.merge(action: action, target_id: record.id, target_type: record.class.name)
.merge(action: action, fingerprint: fingerprint, target_id: record.id, target_type: record.class.name)
end
result = Event.insert_all(attribute_sets, returning: %w[id])
pairs.each do |record, status|
tuples.each do |record, status, _|
Gitlab::UsageDataCounters::TrackUniqueActions.track_action(event_action: status, event_target: record.class, author_id: current_user.id)
end
@ -198,7 +187,11 @@ class EventCreateService
)
attributes.merge!(parent_attrs(resource_parent))
Event.create!(attributes)
if attributes[:fingerprint].present?
Event.safe_find_or_create_by!(attributes)
else
Event.create!(attributes)
end
end
def parent_attrs(resource_parent)

View File

@ -41,7 +41,12 @@ module Git
end
def create_event_for(change)
event_service.execute(change.last_known_slug, change.page, change.event_action)
event_service.execute(
change.last_known_slug,
change.page,
change.event_action,
change.sha
)
end
def event_service

View File

@ -33,6 +33,10 @@ module Git
strip_extension(raw_change.old_path || raw_change.new_path)
end
def sha
change[:newrev]
end
private
attr_reader :raw_change, :change, :wiki

View File

@ -44,7 +44,9 @@ module WikiPages
end
def create_wiki_event(page)
response = WikiPages::EventCreateService.new(current_user).execute(slug_for_page(page), page, event_action)
response = WikiPages::EventCreateService
.new(current_user)
.execute(slug_for_page(page), page, event_action, fingerprint(page))
log_error(response.message) if response.error?
end
@ -52,6 +54,10 @@ module WikiPages
def slug_for_page(page)
page.slug
end
def fingerprint(page)
page.sha
end
end
end

View File

@ -21,5 +21,9 @@ module WikiPages
def event_action
:destroyed
end
def fingerprint(page)
page.wiki.repository.head_commit.sha
end
end
end

View File

@ -9,11 +9,11 @@ module WikiPages
@author = author
end
def execute(slug, page, action)
def execute(slug, page, action, event_fingerprint)
event = Event.transaction do
wiki_page_meta = WikiPage::Meta.find_or_create(slug, page)
::EventCreateService.new.wiki_event(wiki_page_meta, author, action)
::EventCreateService.new.wiki_event(wiki_page_meta, author, action, event_fingerprint)
end
ServiceResponse.success(payload: { event: event })

View File

@ -45,13 +45,13 @@
= _('MERGED')
- elsif merge_request.closed?
%li.issuable-status.d-none.d-sm-inline-block
= icon('ban')
= sprite_icon('cancel', size: 16, css_class: 'gl-vertical-align-text-bottom')
= _('CLOSED')
= render 'shared/merge_request_pipeline_status', merge_request: merge_request
- if merge_request.open? && merge_request.broken?
%li.issuable-pipeline-broken.d-none.d-sm-flex
= link_to merge_request_path(merge_request), class: "has-tooltip", title: _('Cannot be merged automatically') do
= icon('exclamation-triangle')
= sprite_icon('warning-solid', size: 16)
- if merge_request.assignees.any?
%li.d-flex
= render 'shared/issuable/assignees', project: merge_request.project, issuable: merge_request

View File

@ -1,6 +1,6 @@
.row
.col-lg-12
.gl-alert.gl-alert-info.js-alerts-moved-alert{ role: 'alert' }
.gl-alert.gl-alert-info{ role: 'alert' }
= sprite_icon('information-o', size: 16, css_class: 'gl-icon gl-alert-icon gl-alert-icon-no-title')
.gl-alert-body
= _('You can now manage alert endpoint configuration in the Alerts section on the Operations settings page. Fields on this page have been deprecated.')

View File

@ -2,7 +2,7 @@
.row
.col-lg-12
.gl-alert.gl-alert-info.js-alerts-moved-alert{ role: 'alert' }
.gl-alert.gl-alert-info{ role: 'alert' }
= sprite_icon('information-o', size: 16, css_class: 'gl-icon gl-alert-icon gl-alert-icon-no-title')
.gl-alert-body
= s_('AlertSettings|You can now set up alert endpoints for manually configured Prometheus instances in the Alerts section on the Operations settings page. Alert endpoint fields on this page have been deprecated.')

View File

@ -0,0 +1,5 @@
---
title: Replace fa-ban icons with "cancel" from GitLab SVG
merge_request: 37067
author:
type: changed

View File

@ -0,0 +1,5 @@
---
title: Use fingerprint column on events to ensure event uniqueness
merge_request: 31021
author:
type: changed

View File

@ -0,0 +1,5 @@
---
title: Exclude todos from general analytics accumulator ping
merge_request: 36813
author:
type: changed

View File

@ -0,0 +1,36 @@
# frozen_string_literal: true
class AddFingerprintToEvents < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
unless column_exists?(:events, :fingerprint)
with_lock_retries { add_column :events, :fingerprint, :binary }
end
unless check_constraint_exists?(:events, constraint_name)
add_check_constraint(
:events,
"octet_length(fingerprint) <= 128",
constraint_name,
validate: true
)
end
end
def down
remove_check_constraint(:events, constraint_name)
if column_exists?(:events, :fingerprint)
with_lock_retries { remove_column :events, :fingerprint }
end
end
def constraint_name
check_constraint_name(:events, :fingerprint, 'max_length')
end
end

View File

@ -0,0 +1,19 @@
# frozen_string_literal: true
class AddIndexOnFingerprintAndTargetTypeToEvents < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
KEYS = [:target_type, :target_id, :fingerprint]
def up
add_concurrent_index :events, KEYS, using: :btree, unique: true
end
def down
remove_concurrent_index :events, KEYS
end
end

View File

@ -11405,7 +11405,9 @@ CREATE TABLE public.events (
updated_at timestamp with time zone NOT NULL,
action smallint NOT NULL,
target_type character varying,
group_id bigint
group_id bigint,
fingerprint bytea,
CONSTRAINT check_97e06e05ad CHECK ((octet_length(fingerprint) <= 128))
);
CREATE SEQUENCE public.events_id_seq
@ -19327,6 +19329,8 @@ CREATE INDEX index_events_on_project_id_and_id ON public.events USING btree (pro
CREATE INDEX index_events_on_target_type_and_target_id ON public.events USING btree (target_type, target_id);
CREATE UNIQUE INDEX index_events_on_target_type_and_target_id_and_fingerprint ON public.events USING btree (target_type, target_id, fingerprint);
CREATE INDEX index_evidences_on_release_id ON public.evidences USING btree (release_id);
CREATE INDEX index_expired_and_not_notified_personal_access_tokens ON public.personal_access_tokens USING btree (id, expires_at) WHERE ((impersonation = false) AND (revoked = false) AND (expire_notification_delivered = false));
@ -23704,6 +23708,8 @@ COPY "schema_migrations" (version) FROM STDIN;
20200430123614
20200430130048
20200430174637
20200504191813
20200504200709
20200505164958
20200505171834
20200505172405

View File

@ -12063,6 +12063,7 @@ The type of the security scanner.
"""
enum SecurityScannerType {
CONTAINER_SCANNING
COVERAGE_FUZZING
DAST
DEPENDENCY_SCANNING
SAST
@ -14937,7 +14938,7 @@ enum VulnerabilityIssueLinkType {
"""
Represents a vulnerability location. The fields with data will depend on the vulnerability report type
"""
union VulnerabilityLocation = VulnerabilityLocationContainerScanning | VulnerabilityLocationDast | VulnerabilityLocationDependencyScanning | VulnerabilityLocationSast | VulnerabilityLocationSecretDetection
union VulnerabilityLocation = VulnerabilityLocationContainerScanning | VulnerabilityLocationCoverageFuzzing | VulnerabilityLocationDast | VulnerabilityLocationDependencyScanning | VulnerabilityLocationSast | VulnerabilityLocationSecretDetection
"""
Represents the location of a vulnerability found by a container security scan
@ -14959,6 +14960,36 @@ type VulnerabilityLocationContainerScanning {
operatingSystem: String
}
"""
Represents the location of a vulnerability found by a Coverage Fuzzing scan
"""
type VulnerabilityLocationCoverageFuzzing {
"""
Number of the last relevant line in the vulnerable file
"""
endLine: String
"""
Path to the vulnerable file
"""
file: String
"""
Number of the first relevant line in the vulnerable file
"""
startLine: String
"""
Class containing the vulnerability
"""
vulnerableClass: String
"""
Method containing the vulnerability
"""
vulnerableMethod: String
}
"""
Represents the location of a vulnerability found by a DAST scan
"""

View File

@ -35342,6 +35342,12 @@
"description": null,
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "COVERAGE_FUZZING",
"description": null,
"isDeprecated": false,
"deprecationReason": null
}
],
"possibleTypes": null
@ -43978,6 +43984,11 @@
"name": "VulnerabilityLocationContainerScanning",
"ofType": null
},
{
"kind": "OBJECT",
"name": "VulnerabilityLocationCoverageFuzzing",
"ofType": null
},
{
"kind": "OBJECT",
"name": "VulnerabilityLocationDast",
@ -44055,6 +44066,89 @@
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "VulnerabilityLocationCoverageFuzzing",
"description": "Represents the location of a vulnerability found by a Coverage Fuzzing scan",
"fields": [
{
"name": "endLine",
"description": "Number of the last relevant line in the vulnerable file",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "file",
"description": "Path to the vulnerable file",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "startLine",
"description": "Number of the first relevant line in the vulnerable file",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "vulnerableClass",
"description": "Class containing the vulnerability",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "vulnerableMethod",
"description": "Method containing the vulnerability",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "VulnerabilityLocationDast",

View File

@ -2246,6 +2246,18 @@ Represents the location of a vulnerability found by a container security scan
| `image` | String | Name of the vulnerable container image |
| `operatingSystem` | String | Operating system that runs on the vulnerable container image |
## VulnerabilityLocationCoverageFuzzing
Represents the location of a vulnerability found by a Coverage Fuzzing scan
| Name | Type | Description |
| --- | ---- | ---------- |
| `endLine` | String | Number of the last relevant line in the vulnerable file |
| `file` | String | Path to the vulnerable file |
| `startLine` | String | Number of the first relevant line in the vulnerable file |
| `vulnerableClass` | String | Class containing the vulnerability |
| `vulnerableMethod` | String | Method containing the vulnerability |
## VulnerabilityLocationDast
Represents the location of a vulnerability found by a DAST scan

View File

@ -618,6 +618,7 @@ appear to be associated to any of the services running, since they all appear to
| `sd` | `avg_cycle_analytics - production` | | | | |
| `missing` | `avg_cycle_analytics - production` | | | | |
| `total` | `avg_cycle_analytics` | | | | |
| `u_todos` | | `manage` | | | Visits to /dashboard/todos |
| `g_analytics_contribution` | `analytics_unique_visits` | `manage` | | | Visits to /groups/:group/-/contribution_analytics |
| `g_analytics_insights` | `analytics_unique_visits` | `manage` | | | Visits to /groups/:group/-/insights |
| `g_analytics_issues` | `analytics_unique_visits` | `manage` | | | Visits to /groups/:group/-/issues_analytics |
@ -629,7 +630,6 @@ appear to be associated to any of the services running, since they all appear to
| `p_analytics_insights` | `analytics_unique_visits` | `manage` | | | Visits to /:group/:project/insights |
| `p_analytics_issues` | `analytics_unique_visits` | `manage` | | | Visits to /:group/:project/-/analytics/issues_analytics |
| `p_analytics_repo` | `analytics_unique_visits` | `manage` | | | Visits to /:group/:project/-/graphs/master/charts |
| `u_analytics_todos` | `analytics_unique_visits` | `manage` | | | Visits to /dashboard/todos |
| `i_analytics_cohorts` | `analytics_unique_visits` | `manage` | | | Visits to /-/instance_statistics/cohorts |
| `i_analytics_dev_ops_score` | `analytics_unique_visits` | `manage` | | | Visits to /-/instance_statistics/dev_ops_score |
| `analytics_unique_visits_for_any_target` | `analytics_unique_visits` | `manage` | | | Visits to any of the pages listed above |

View File

@ -387,6 +387,9 @@ analyzer containers: `DOCKER_`, `CI`, `GITLAB_`, `FF_`, `HOME`, `PWD`, `OLDPWD`,
The SAST tool emits a JSON report file. For more information, see the
[schema for this report](https://gitlab.com/gitlab-org/security-products/security-report-schemas/-/blob/master/dist/sast-report-format.json).
The JSON report file can be downloaded from the CI pipelines page, for more
information see [Downloading artifacts](../../../ci/pipelines/job_artifacts.md).
Here's an example SAST report:
```json-doc

View File

@ -15,7 +15,7 @@ module Gitlab
'p_analytics_insights',
'p_analytics_issues',
'p_analytics_repo',
'u_analytics_todos',
'u_todos',
'i_analytics_cohorts',
'i_analytics_dev_ops_score'
].freeze
@ -40,7 +40,7 @@ module Gitlab
end
def weekly_unique_visits_for_any_target(week_of: 7.days.ago)
keys = TARGET_IDS.map { |target_id| key(target_id, week_of) }
keys = TARGET_IDS.select { |id| id =~ /_analytics_/ }.map { |target_id| key(target_id, week_of) }
Gitlab::Redis::SharedState.with do |redis|
redis.pfcount(*keys)

View File

@ -2937,6 +2937,9 @@ msgstr ""
msgid "Applying suggestions..."
msgstr ""
msgid "Approval Status"
msgstr ""
msgid "Approval rules"
msgstr ""
@ -2984,6 +2987,15 @@ msgstr ""
msgid "ApprovalRule|e.g. QA, Security, etc."
msgstr ""
msgid "ApprovalStatusTooltip|Adheres to separation of duties"
msgstr ""
msgid "ApprovalStatusTooltip|At least one rule does not adhere to separation of duties"
msgstr ""
msgid "ApprovalStatusTooltip|Fails to adhere to separation of duties"
msgstr ""
msgid "Approvals|Section: %section"
msgstr ""

View File

@ -48,7 +48,7 @@ RSpec.describe Dashboard::TodosController do
it_behaves_like 'tracking unique visits', :index do
let(:request_params) { { project_id: authorized_project.id } }
let(:target_id) { 'u_analytics_todos' }
let(:target_id) { 'u_todos' }
end
end
end

View File

@ -41,6 +41,7 @@ Event:
- updated_at
- action
- author_id
- fingerprint
WikiPage::Meta:
- id
- title

View File

@ -994,7 +994,7 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
'p_analytics_insights' => 123,
'p_analytics_issues' => 123,
'p_analytics_repo' => 123,
'u_analytics_todos' => 123,
'u_todos' => 123,
'i_analytics_cohorts' => 123,
'i_analytics_dev_ops_score' => 123,
'analytics_unique_visits_for_any_target' => 543

View File

@ -111,6 +111,45 @@ RSpec.describe Event do
expect(found).not_to include(false_positive)
end
end
describe '.for_fingerprint' do
let_it_be(:with_fingerprint) { create(:event, fingerprint: 'aaa') }
before_all do
create(:event)
create(:event, fingerprint: 'bbb')
end
it 'returns none if there is no fingerprint' do
expect(described_class.for_fingerprint(nil)).to be_empty
expect(described_class.for_fingerprint('')).to be_empty
end
it 'returns none if there is no match' do
expect(described_class.for_fingerprint('not-found')).to be_empty
end
it 'can find a given event' do
expect(described_class.for_fingerprint(with_fingerprint.fingerprint))
.to contain_exactly(with_fingerprint)
end
end
end
describe '#fingerprint' do
it 'is unique scoped to target' do
issue = create(:issue)
mr = create(:merge_request)
expect { create_list(:event, 2, target: issue, fingerprint: '1234') }
.to raise_error(include('fingerprint'))
expect do
create(:event, target: mr, fingerprint: 'abcd')
create(:event, target: issue, fingerprint: 'abcd')
create(:event, target: issue, fingerprint: 'efgh')
end.not_to raise_error
end
end
describe "Push event" do

View File

@ -45,10 +45,11 @@ RSpec.describe API::ProjectMilestones do
describe 'PUT /projects/:id/milestones/:milestone_id to test observer on close' do
it 'creates an activity event when a milestone is closed' do
expect(Event).to receive(:create!)
path = "/projects/#{project.id}/milestones/#{milestone.id}"
put api("/projects/#{project.id}/milestones/#{milestone.id}", user),
params: { state_event: 'close' }
expect do
put api(path, user), params: { state_event: 'close' }
end.to change(Event, :count).by(1)
end
end

View File

@ -171,45 +171,53 @@ RSpec.describe EventCreateService do
let_it_be(:wiki_page) { create(:wiki_page) }
let_it_be(:meta) { create(:wiki_page_meta, :for_wiki_page, wiki_page: wiki_page) }
Event::WIKI_ACTIONS.each do |action|
context "The action is #{action}" do
let(:event) { service.wiki_event(meta, user, action) }
let(:fingerprint) { generate(:sha) }
it 'creates the event', :aggregate_failures do
expect(event).to have_attributes(
wiki_page?: true,
valid?: true,
persisted?: true,
action: action.to_s,
wiki_page: wiki_page,
author: user
)
end
def create_event
service.wiki_event(meta, user, action, fingerprint)
end
it 'records the event in the event counter' do
stub_feature_flags(Gitlab::UsageDataCounters::TrackUniqueActions::FEATURE_FLAG => true)
counter_class = Gitlab::UsageDataCounters::TrackUniqueActions
tracking_params = { event_action: counter_class::WIKI_ACTION, date_from: Date.yesterday, date_to: Date.today }
where(:action) { Event::WIKI_ACTIONS.map { |action| [action] } }
expect { event }
.to change { counter_class.count_unique_events(tracking_params) }
.from(0).to(1)
end
with_them do
it 'creates the event' do
expect(create_event).to have_attributes(
wiki_page?: true,
valid?: true,
persisted?: true,
action: action.to_s,
wiki_page: wiki_page,
author: user,
fingerprint: fingerprint
)
end
it 'is idempotent', :aggregate_failures do
expect { event }.to change(Event, :count).by(1)
duplicate = nil
expect { duplicate = service.wiki_event(meta, user, action) }.not_to change(Event, :count)
it 'is idempotent', :aggregate_failures do
event = nil
expect { event = create_event }.to change(Event, :count).by(1)
duplicate = nil
expect { duplicate = create_event }.not_to change(Event, :count)
expect(duplicate).to eq(event)
end
expect(duplicate).to eq(event)
end
it 'records the event in the event counter' do
stub_feature_flags(Gitlab::UsageDataCounters::TrackUniqueActions::FEATURE_FLAG => true)
counter_class = Gitlab::UsageDataCounters::TrackUniqueActions
tracking_params = { event_action: counter_class::WIKI_ACTION, date_from: Date.yesterday, date_to: Date.today }
expect { create_event }
.to change { counter_class.count_unique_events(tracking_params) }
.by(1)
end
end
(Event.actions.keys - Event::WIKI_ACTIONS).each do |bad_action|
context "The action is #{bad_action}" do
let(:action) { bad_action }
it 'raises an error' do
expect { service.wiki_event(meta, user, bad_action) }.to raise_error(described_class::IllegalActionError)
expect { create_event }.to raise_error(described_class::IllegalActionError)
end
end
end

View File

@ -218,7 +218,7 @@ RSpec.describe Git::WikiPushService, services: true do
message = 'something went very very wrong'
allow_next_instance_of(WikiPages::EventCreateService, current_user) do |service|
allow(service).to receive(:execute)
.with(String, WikiPage, Symbol)
.with(String, WikiPage, Symbol, String)
.and_return(ServiceResponse.error(message: message))
end

View File

@ -12,7 +12,8 @@ RSpec.describe WikiPages::EventCreateService do
let_it_be(:page) { create(:wiki_page, project: project) }
let(:slug) { generate(:sluggified_title) }
let(:action) { :created }
let(:response) { subject.execute(slug, page, action) }
let(:fingerprint) { page.sha }
let(:response) { subject.execute(slug, page, action, fingerprint) }
context 'the user is nil' do
subject { described_class.new(nil) }

View File

@ -158,9 +158,11 @@ RSpec.shared_examples 'noteable API' do |parent_type, noteable_type, id_name|
end
it "creates an activity event when a note is created", :sidekiq_might_not_need_inline do
expect(Event).to receive(:create!)
uri = "/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes"
post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user), params: { body: 'hi!' }
expect do
post api(uri, user), params: { body: 'hi!' }
end.to change(Event, :count).by(1)
end
context 'setting created_at' do