提交 c780abc8 编写于 作者: G GitLab Bot

Add latest changes from gitlab-org/gitlab@master

上级 a7608a49
9e6f5f40e6eb44655b6acfd5dc222af04333a4f2
521bb978da8780aca690136e78a3ad388726c8ad
......@@ -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'
......
......@@ -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
......
......@@ -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>
......
......@@ -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',
];
......
......@@ -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]
......
......@@ -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
......
......@@ -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?
......
......@@ -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)
......
......@@ -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
......
......@@ -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
......
......@@ -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
......
......@@ -21,5 +21,9 @@ module WikiPages
def event_action
:destroyed
end
def fingerprint(page)
page.wiki.repository.head_commit.sha
end
end
end
......@@ -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 })
......
......@@ -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
......
.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.')
......
......@@ -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.')
......
---
title: Replace fa-ban icons with "cancel" from GitLab SVG
merge_request: 37067
author:
type: changed
---
title: Use fingerprint column on events to ensure event uniqueness
merge_request: 31021
author:
type: changed
---
title: Exclude todos from general analytics accumulator ping
merge_request: 36813
author:
type: changed
# 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
# 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
......@@ -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
......
......@@ -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
"""
......
......@@ -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",
......@@ -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
......
......@@ -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 |
......
......@@ -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
......
......@@ -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)
......
......@@ -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 ""
......
......@@ -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
......
......@@ -41,6 +41,7 @@ Event:
- updated_at
- action
- author_id
- fingerprint
WikiPage::Meta:
- id
- title
......
......@@ -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
......
......@@ -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
......
......@@ -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
......
......@@ -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) }
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
let(:fingerprint) { generate(:sha) }
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 }
def create_event
service.wiki_event(meta, user, action, fingerprint)
end
expect { event }
.to change { counter_class.count_unique_events(tracking_params) }
.from(0).to(1)
end
where(:action) { Event::WIKI_ACTIONS.map { |action| [action] } }
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
......
......@@ -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
......
......@@ -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) }
......
......@@ -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
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册