Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
5831f05b4c
commit
84b507d17b
|
|
@ -67,7 +67,6 @@ Layout/FirstHashElementIndentation:
|
|||
- 'ee/spec/controllers/projects_controller_spec.rb'
|
||||
- 'ee/spec/elastic/migrate/migration_shared_examples.rb'
|
||||
- 'ee/spec/factories/dependencies.rb'
|
||||
- 'ee/spec/factories/licenses.rb'
|
||||
- 'ee/spec/finders/epics_finder_spec.rb'
|
||||
- 'ee/spec/finders/namespaces/free_user_cap/users_finder_spec.rb'
|
||||
- 'ee/spec/frontend/fixtures/oncall_schedule.rb'
|
||||
|
|
|
|||
|
|
@ -520,14 +520,6 @@ RSpec/FeatureCategory:
|
|||
- 'ee/spec/lib/banzai/reference_parser/epic_parser_spec.rb'
|
||||
- 'ee/spec/lib/banzai/reference_parser/iteration_parser_spec.rb'
|
||||
- 'ee/spec/lib/banzai/reference_parser/vulnerability_parser_spec.rb'
|
||||
- 'ee/spec/lib/bulk_imports/common/pipelines/boards_pipeline_spec.rb'
|
||||
- 'ee/spec/lib/bulk_imports/common/pipelines/wiki_pipeline_spec.rb'
|
||||
- 'ee/spec/lib/bulk_imports/groups/graphql/get_iterations_query_spec.rb'
|
||||
- 'ee/spec/lib/bulk_imports/groups/pipelines/epics_pipeline_spec.rb'
|
||||
- 'ee/spec/lib/bulk_imports/groups/pipelines/iterations_pipeline_spec.rb'
|
||||
- 'ee/spec/lib/bulk_imports/projects/pipelines/issues_pipeline_spec.rb'
|
||||
- 'ee/spec/lib/bulk_imports/projects/pipelines/protected_branches_pipeline_spec.rb'
|
||||
- 'ee/spec/lib/bulk_imports/projects/pipelines/push_rule_pipeline_spec.rb'
|
||||
- 'ee/spec/lib/compliance_management/merge_request_approval_settings/resolver_spec.rb'
|
||||
- 'ee/spec/lib/container_registry/client_spec.rb'
|
||||
- 'ee/spec/lib/ee/api/entities/analytics/code_review/merge_request_spec.rb'
|
||||
|
|
@ -2614,9 +2606,6 @@ RSpec/FeatureCategory:
|
|||
- 'spec/lib/bulk_imports/common/extractors/json_extractor_spec.rb'
|
||||
- 'spec/lib/bulk_imports/common/extractors/ndjson_extractor_spec.rb'
|
||||
- 'spec/lib/bulk_imports/common/extractors/rest_extractor_spec.rb'
|
||||
- 'spec/lib/bulk_imports/common/pipelines/badges_pipeline_spec.rb'
|
||||
- 'spec/lib/bulk_imports/common/pipelines/lfs_objects_pipeline_spec.rb'
|
||||
- 'spec/lib/bulk_imports/common/pipelines/members_pipeline_spec.rb'
|
||||
- 'spec/lib/bulk_imports/common/rest/get_badges_query_spec.rb'
|
||||
- 'spec/lib/bulk_imports/common/transformers/prohibited_attributes_transformer_spec.rb'
|
||||
- 'spec/lib/bulk_imports/file_downloads/filename_fetch_spec.rb'
|
||||
|
|
@ -2624,10 +2613,6 @@ RSpec/FeatureCategory:
|
|||
- 'spec/lib/bulk_imports/groups/extractors/subgroups_extractor_spec.rb'
|
||||
- 'spec/lib/bulk_imports/groups/graphql/get_group_query_spec.rb'
|
||||
- 'spec/lib/bulk_imports/groups/graphql/get_projects_query_spec.rb'
|
||||
- 'spec/lib/bulk_imports/groups/pipelines/group_attributes_pipeline_spec.rb'
|
||||
- 'spec/lib/bulk_imports/groups/pipelines/group_pipeline_spec.rb'
|
||||
- 'spec/lib/bulk_imports/groups/pipelines/namespace_settings_pipeline_spec.rb'
|
||||
- 'spec/lib/bulk_imports/groups/pipelines/subgroup_entities_pipeline_spec.rb'
|
||||
- 'spec/lib/bulk_imports/groups/transformers/subgroup_to_entity_transformer_spec.rb'
|
||||
- 'spec/lib/bulk_imports/pipeline/context_spec.rb'
|
||||
- 'spec/lib/bulk_imports/pipeline/extracted_data_spec.rb'
|
||||
|
|
@ -2635,19 +2620,6 @@ RSpec/FeatureCategory:
|
|||
- 'spec/lib/bulk_imports/projects/graphql/get_project_query_spec.rb'
|
||||
- 'spec/lib/bulk_imports/projects/graphql/get_repository_query_spec.rb'
|
||||
- 'spec/lib/bulk_imports/projects/graphql/get_snippet_repository_query_spec.rb'
|
||||
- 'spec/lib/bulk_imports/projects/pipelines/auto_devops_pipeline_spec.rb'
|
||||
- 'spec/lib/bulk_imports/projects/pipelines/ci_pipelines_pipeline_spec.rb'
|
||||
- 'spec/lib/bulk_imports/projects/pipelines/design_bundle_pipeline_spec.rb'
|
||||
- 'spec/lib/bulk_imports/projects/pipelines/issues_pipeline_spec.rb'
|
||||
- 'spec/lib/bulk_imports/projects/pipelines/merge_requests_pipeline_spec.rb'
|
||||
- 'spec/lib/bulk_imports/projects/pipelines/project_attributes_pipeline_spec.rb'
|
||||
- 'spec/lib/bulk_imports/projects/pipelines/project_feature_pipeline_spec.rb'
|
||||
- 'spec/lib/bulk_imports/projects/pipelines/protected_branches_pipeline_spec.rb'
|
||||
- 'spec/lib/bulk_imports/projects/pipelines/releases_pipeline_spec.rb'
|
||||
- 'spec/lib/bulk_imports/projects/pipelines/repository_bundle_pipeline_spec.rb'
|
||||
- 'spec/lib/bulk_imports/projects/pipelines/service_desk_setting_pipeline_spec.rb'
|
||||
- 'spec/lib/bulk_imports/projects/pipelines/snippets_pipeline_spec.rb'
|
||||
- 'spec/lib/bulk_imports/projects/pipelines/snippets_repository_pipeline_spec.rb'
|
||||
- 'spec/lib/bulk_imports/retry_pipeline_error_spec.rb'
|
||||
- 'spec/lib/bulk_imports/users_mapper_spec.rb'
|
||||
- 'spec/lib/constraints/admin_constrainer_spec.rb'
|
||||
|
|
|
|||
|
|
@ -122,6 +122,12 @@ export const MERGE_REQUEST_METRICS = {
|
|||
THROUGHPUT: MERGE_REQUEST_THROUGHPUT_TYPE,
|
||||
};
|
||||
|
||||
export const CONTRIBUTOR_COUNT_TYPE = 'contributor_count';
|
||||
|
||||
export const CONTRIBUTOR_METRICS = {
|
||||
COUNT: CONTRIBUTOR_COUNT_TYPE,
|
||||
};
|
||||
|
||||
export const METRIC_TOOLTIPS = {
|
||||
[DORA_METRICS.DEPLOYMENT_FREQUENCY]: {
|
||||
description: s__(
|
||||
|
|
@ -193,6 +199,15 @@ export const METRIC_TOOLTIPS = {
|
|||
projectLink: '-/analytics/merge_request_analytics',
|
||||
docsLink: helpPagePath('user/analytics/merge_request_analytics'),
|
||||
},
|
||||
[CONTRIBUTOR_METRICS.COUNT]: {
|
||||
description: s__(
|
||||
'ValueStreamAnalytics|Number of monthly unique users with contributions in the group.',
|
||||
),
|
||||
groupLink: '-/contribution_analytics',
|
||||
docsLink: helpPagePath('user/profile/contributions_calendar.html', {
|
||||
anchor: 'user-contribution-events',
|
||||
}),
|
||||
},
|
||||
[VULNERABILITY_METRICS.CRITICAL]: {
|
||||
description: s__('ValueStreamAnalytics|Critical vulnerabilities over time.'),
|
||||
groupLink: '-/security/vulnerabilities?severity=CRITICAL',
|
||||
|
|
|
|||
|
|
@ -219,7 +219,7 @@ class GfmAutoComplete {
|
|||
let tpl = '/${name} ';
|
||||
let referencePrefix = null;
|
||||
if (value.params.length > 0) {
|
||||
const regexp = /\[[a-z]+:/;
|
||||
const regexp = /^<\[[a-z]+:/;
|
||||
const match = regexp.exec(value.params);
|
||||
if (match) {
|
||||
[referencePrefix] = match;
|
||||
|
|
|
|||
|
|
@ -207,6 +207,16 @@ class BulkImports::Entity < ApplicationRecord
|
|||
@source_version ||= bulk_import.source_version_info
|
||||
end
|
||||
|
||||
def checksums
|
||||
trackers.each_with_object({}) do |tracker, checksums|
|
||||
next unless tracker.file_extraction_pipeline?
|
||||
next if tracker.skipped?
|
||||
next if tracker.checksums_empty?
|
||||
|
||||
checksums.merge!(tracker.checksums)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def validate_parent_is_a_group
|
||||
|
|
|
|||
|
|
@ -46,6 +46,10 @@ module BulkImports
|
|||
status['batches'].find { |item| item['batch_number'] == batch_number }
|
||||
end
|
||||
|
||||
def total_objects_count
|
||||
status['total_objects_count']
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :client, :entity, :relation, :pipeline_tracker
|
||||
|
|
|
|||
|
|
@ -86,5 +86,42 @@ class BulkImports::Tracker < ApplicationRecord
|
|||
event :cleanup_stale do
|
||||
transition [:created, :started] => :timeout
|
||||
end
|
||||
|
||||
after_transition any => [:finished, :failed] do |tracker|
|
||||
BulkImports::ObjectCounter.persist!(tracker)
|
||||
end
|
||||
end
|
||||
|
||||
def checksums
|
||||
return unless file_extraction_pipeline?
|
||||
|
||||
# Return cached counters until they expire
|
||||
{ importing_relation => cached_checksums || persisted_checksums }
|
||||
end
|
||||
|
||||
def checksums_empty?
|
||||
return true unless checksums
|
||||
|
||||
sums = checksums[importing_relation]
|
||||
|
||||
sums[:source] == 0 && sums[:fetched] == 0 && sums[:imported] == 0
|
||||
end
|
||||
|
||||
def importing_relation
|
||||
pipeline_class.relation.to_sym
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def cached_checksums
|
||||
BulkImports::ObjectCounter.summary(self)
|
||||
end
|
||||
|
||||
def persisted_checksums
|
||||
{
|
||||
source: source_objects_count,
|
||||
fetched: fetched_objects_count,
|
||||
imported: imported_objects_count
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ module TimeTrackable
|
|||
@spent_at = options[:spent_at]
|
||||
@summary = options[:summary]
|
||||
@original_total_time_spent = nil
|
||||
@category_id = category_id(options[:category])
|
||||
|
||||
return if @time_spent == 0
|
||||
|
||||
|
|
@ -94,7 +95,8 @@ module TimeTrackable
|
|||
note_id: @time_spent_note_id,
|
||||
user: @time_spent_user,
|
||||
spent_at: @spent_at,
|
||||
summary: @summary
|
||||
summary: @summary,
|
||||
timelog_category_id: @category_id
|
||||
)
|
||||
end
|
||||
# rubocop:enable Gitlab/ModuleWithInstanceVariables
|
||||
|
|
@ -119,4 +121,8 @@ module TimeTrackable
|
|||
|
||||
errors.add(:time_estimate, _('must have a valid format and be greater than or equal to zero.'))
|
||||
end
|
||||
|
||||
def category_id(category)
|
||||
TimeTracking::TimelogCategory.find_by_name(project.root_namespace, category).first&.id
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -82,7 +82,12 @@ module BulkImports
|
|||
end
|
||||
|
||||
def finish_export!(export)
|
||||
export.update!(status_event: 'finish', batched: false, error: nil)
|
||||
export.update!(
|
||||
status_event: 'finish',
|
||||
batched: false,
|
||||
error: nil,
|
||||
total_objects_count: export_service.exported_objects_count
|
||||
)
|
||||
end
|
||||
|
||||
def exported_filepath
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ Gitlab::HTTP_V2.configure do |config|
|
|||
end
|
||||
end
|
||||
|
||||
if Gitlab.config.gitlab['http_client']
|
||||
if Gitlab.config.gitlab['http_client'].present?
|
||||
pem = File.read(Gitlab.config.gitlab['http_client']['tls_client_cert_file'])
|
||||
password = Gitlab.config.gitlab['http_client']['tls_client_cert_password']
|
||||
|
||||
|
|
|
|||
|
|
@ -7,4 +7,12 @@ feature_categories:
|
|||
description: Represents a Terraform state backend
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/26619
|
||||
milestone: '13.0'
|
||||
gitlab_schema: gitlab_main
|
||||
gitlab_schema: gitlab_main_cell
|
||||
allow_cross_joins:
|
||||
- gitlab_main_clusterwide
|
||||
allow_cross_transactions:
|
||||
- gitlab_main_clusterwide
|
||||
allow_cross_foreign_keys:
|
||||
- gitlab_main_clusterwide
|
||||
sharding_key:
|
||||
project_id: projects
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddObjectCountFieldsToBulkImportTrackers < Gitlab::Database::Migration[2.2]
|
||||
milestone '16.8'
|
||||
|
||||
def change
|
||||
add_column :bulk_import_trackers, :source_objects_count, :bigint, null: false, default: 0
|
||||
add_column :bulk_import_trackers, :fetched_objects_count, :bigint, null: false, default: 0
|
||||
add_column :bulk_import_trackers, :imported_objects_count, :bigint, null: false, default: 0
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
01a7d610bdf3c5d8e5f98f2c479a7cf83b7591e791b6f8e18f836b6bf6f52833
|
||||
|
|
@ -13953,6 +13953,9 @@ CREATE TABLE bulk_import_trackers (
|
|||
created_at timestamp with time zone,
|
||||
updated_at timestamp with time zone,
|
||||
batched boolean DEFAULT false,
|
||||
source_objects_count bigint DEFAULT 0 NOT NULL,
|
||||
fetched_objects_count bigint DEFAULT 0 NOT NULL,
|
||||
imported_objects_count bigint DEFAULT 0 NOT NULL,
|
||||
CONSTRAINT check_2d45cae629 CHECK ((char_length(relation) <= 255)),
|
||||
CONSTRAINT check_40aeaa600b CHECK ((char_length(next_page) <= 255)),
|
||||
CONSTRAINT check_603f91cb06 CHECK ((char_length(jid) <= 255)),
|
||||
|
|
|
|||
|
|
@ -152,7 +152,19 @@ curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab
|
|||
"project_id": null,
|
||||
"created_at": "2021-06-18T09:47:37.390Z",
|
||||
"updated_at": "2021-06-18T09:47:51.867Z",
|
||||
"failures": []
|
||||
"failures": [],
|
||||
"stats": {
|
||||
"labels": {
|
||||
"source": 10,
|
||||
"fetched": 10,
|
||||
"imported": 10
|
||||
},
|
||||
"milestones": {
|
||||
"source": 10,
|
||||
"fetched": 10,
|
||||
"imported": 10
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
|
|
@ -233,7 +245,19 @@ curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab
|
|||
"status": "finished",
|
||||
"source_type": "gitlab",
|
||||
"created_at": "2021-06-18T09:45:55.358Z",
|
||||
"updated_at": "2021-06-18T09:46:27.003Z"
|
||||
"updated_at": "2021-06-18T09:46:27.003Z",
|
||||
"stats": {
|
||||
"labels": {
|
||||
"source": 10,
|
||||
"fetched": 10,
|
||||
"imported": 10
|
||||
},
|
||||
"milestones": {
|
||||
"source": 10,
|
||||
"fetched": 10,
|
||||
"imported": 10
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
stage: core platform
|
||||
group: Tenant Scale
|
||||
description: 'Cells: Routing Service'
|
||||
status: accepted
|
||||
---
|
||||
|
||||
# Cells: Routing Service
|
||||
|
|
@ -398,7 +399,7 @@ For the above example:
|
|||
{
|
||||
"metadata": {
|
||||
"rule_id": "c9scvaiwj51a75kzoh917uwtnw8z4ebl",
|
||||
"headers": {
|
||||
"headers": {
|
||||
"all_request_headers": "value"
|
||||
},
|
||||
"method": "GET",
|
||||
|
|
|
|||
|
|
@ -35,6 +35,8 @@ The Value Streams Dashboard panels has a default configuration, but you can also
|
|||
|
||||
### DevSecOps metrics comparison panel
|
||||
|
||||
> Contributor count metric [added](https://gitlab.com/gitlab-org/gitlab/-/issues/433353) in GitLab 16.9.
|
||||
|
||||
The DevSecOps metrics comparison displays DORA4, vulnerability, and flow metrics for a group or project in the
|
||||
month-to-date, last month, the month before, and the past 180 days.
|
||||
|
||||
|
|
@ -48,6 +50,9 @@ that are the largest value contributors, overperforming, or underperforming.
|
|||
You can also drill down the metrics for further analysis.
|
||||
When you hover over a metric, a tooltip displays an explanation of the metric and a link to the related documentation page.
|
||||
|
||||
NOTE:
|
||||
The contributor count metric is available only on GitLab.com at the group-level. To view this metric in the comparison panel, you must [set up ClickHouse](../../integration/clickhouse.md), and enable the [feature flags](../../administration/feature_flags.md) `clickhouse_data_collection` and `event_sync_worker_for_click_house`.
|
||||
|
||||
### DORA Performers score panel
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/386843) in GitLab 16.2 [with a flag](../../administration/feature_flags.md) named `dora_performers_score_panel`. Disabled by default.
|
||||
|
|
@ -242,6 +247,7 @@ If the comparison panel from the configuration file is enabled with `filter_labe
|
|||
| Issues closed | Number of issues closed by month. | [Value Stream Analytics](https://gitlab.com/groups/gitlab-org/-/analytics/value_stream_analytics) | [Value Stream Analytics](../group/value_stream_analytics/index.md) | `issues_completed` |
|
||||
| Number of deploys | Total number of deploys to production. | [Merge Request Analytics](https://gitlab.com/gitlab-org/gitlab/-/analytics/merge_request_analytics) | [Merge request analytics](merge_request_analytics.md) | `deploys` |
|
||||
| Merge request throughput | The number of merge requests merged by month. | [Groups Productivity analytics](productivity_analytics.md), [Projects Merge Request Analytics](https://gitlab.com/gitlab-org/gitlab/-/analytics/merge_request_analytics) | [Groups Productivity analytics](productivity_analytics.md) [Projects Merge request analytics](merge_request_analytics.md) | `merge_request_throughput` |
|
||||
| Contributor count | Number of monthly unique users with contributions in the group.| [Contribution Analytics](https://gitlab.com/groups/gitlab-org/-/contribution_analytics) | [User contribution events](../profile/contributions_calendar.md#user-contribution-events) | `contributor_count` |
|
||||
| Critical vulnerabilities over time | Critical vulnerabilities over time in project or group | [Vulnerability report](https://gitlab.com/gitlab-org/gitlab/-/security/vulnerability_report) | [Vulnerability report](../application_security/vulnerability_report/index.md) | `vulnerability_critical` |
|
||||
| High vulnerabilities over time | High vulnerabilities over time in project or group | [Vulnerability report](https://gitlab.com/gitlab-org/gitlab/-/security/vulnerability_report) | [Vulnerability report](../application_security/vulnerability_report/index.md) | `vulnerability_high` |
|
||||
|
||||
|
|
|
|||
|
|
@ -1047,6 +1047,8 @@ the note content.
|
|||
Regardless of the tag names, the relative order of the reference tags determines the rendered
|
||||
numbering.
|
||||
|
||||
Regardless where you put the note, it's always shown at the bottom of the file.
|
||||
|
||||
<!--
|
||||
The following codeblock uses HTML to skip the Vale ReferenceLinks test.
|
||||
Do not change it back to a markdown codeblock.
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ If you use [Slack slash commands](slack_slash_commands.md) or
|
|||
- Replace `/gitlab` with the trigger name you've configured for these integrations.
|
||||
- Remove `<project>`.
|
||||
|
||||
The following slash commands are available in GitLab:
|
||||
The following slash commands are available for GitLab:
|
||||
|
||||
| Command | Description |
|
||||
| ------- | ----------- |
|
||||
|
|
@ -157,7 +157,7 @@ To receive notifications to a private Slack channel, you must add the GitLab for
|
|||
|
||||
### Notification events
|
||||
|
||||
The following GitLab events are available for Slack notifications:
|
||||
The following GitLab events can trigger notifications in Slack:
|
||||
|
||||
| Event | Description |
|
||||
|----------------------------------------------------------------------|---------------------------------------------------------------|
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ module API
|
|||
expose :failures, using: EntityFailure, documentation: { is_array: true }
|
||||
expose :migrate_projects, documentation: { type: 'boolean', example: true }
|
||||
expose :has_failures, documentation: { type: 'boolean', example: false }
|
||||
expose :checksums, as: :stats, documentation: { type: 'object' }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,69 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module BulkImports
|
||||
class ObjectCounter
|
||||
SOURCE_COUNTER = :source
|
||||
FETCHED_COUNTER = :fetched
|
||||
IMPORTED_COUNTER = :imported
|
||||
COUNTER_TYPES = [SOURCE_COUNTER, FETCHED_COUNTER, IMPORTED_COUNTER].freeze
|
||||
CACHE_KEY = 'bulk_imports/object_counter/%{tracker_id}'
|
||||
|
||||
class << self
|
||||
def increment(tracker, counter_type, value = 1)
|
||||
return unless valid_input?(counter_type, value)
|
||||
|
||||
Gitlab::Cache::Import::Caching.hash_increment(counter_key(tracker), counter_type, value)
|
||||
end
|
||||
|
||||
def set(tracker, counter_type, value = 1)
|
||||
return unless valid_input?(counter_type, value)
|
||||
|
||||
Gitlab::Cache::Import::Caching.hash_add(counter_key(tracker), counter_type, value)
|
||||
end
|
||||
|
||||
def summary(tracker)
|
||||
object_counters = Gitlab::Cache::Import::Caching.values_from_hash(counter_key(tracker))
|
||||
|
||||
return unless object_counters.is_a?(Hash)
|
||||
return if object_counters.empty?
|
||||
|
||||
empty_response.merge(object_counters.symbolize_keys.transform_values(&:to_i))
|
||||
end
|
||||
|
||||
# Commits counters from redis to the database
|
||||
def persist!(tracker)
|
||||
counters = summary(tracker)
|
||||
|
||||
return unless counters
|
||||
|
||||
tracker.update!(
|
||||
source_objects_count: counters[SOURCE_COUNTER],
|
||||
fetched_objects_count: counters[FETCHED_COUNTER],
|
||||
imported_objects_count: counters[IMPORTED_COUNTER]
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def counter_key(tracker)
|
||||
Kernel.format(CACHE_KEY, tracker_id: tracker.id)
|
||||
end
|
||||
|
||||
def valid_input?(counter_type, value)
|
||||
return false unless value.is_a?(Integer)
|
||||
return false if value <= 0
|
||||
return false unless COUNTER_TYPES.include?(counter_type)
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
def empty_response
|
||||
{
|
||||
SOURCE_COUNTER => 0,
|
||||
FETCHED_COUNTER => 0,
|
||||
IMPORTED_COUNTER => 0
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -12,6 +12,7 @@ module BulkImports
|
|||
|
||||
info(message: 'Pipeline started')
|
||||
|
||||
set_source_objects_counter
|
||||
extracted_data = extracted_data_from
|
||||
|
||||
if extracted_data
|
||||
|
|
@ -21,6 +22,8 @@ module BulkImports
|
|||
raw_entry = entry.dup
|
||||
next if already_processed?(raw_entry, index)
|
||||
|
||||
increment_fetched_objects_counter
|
||||
|
||||
transformers.each do |transformer|
|
||||
entry = run_pipeline_step(:transformer, transformer.class.name) do
|
||||
transformer.transform(context, entry)
|
||||
|
|
@ -29,6 +32,8 @@ module BulkImports
|
|||
|
||||
run_pipeline_step(:loader, loader.class.name, entry) do
|
||||
loader.load(context, entry)
|
||||
|
||||
increment_imported_objects_counter
|
||||
end
|
||||
|
||||
save_processed_entry(raw_entry, index)
|
||||
|
|
@ -196,6 +201,21 @@ module BulkImports
|
|||
context.entity.touch
|
||||
context.bulk_import.touch
|
||||
end
|
||||
|
||||
def set_source_objects_counter
|
||||
# Export status is cached for 24h and read from Redis at this point
|
||||
export_status = ExportStatus.new(tracker, tracker.importing_relation)
|
||||
|
||||
ObjectCounter.set(tracker, ObjectCounter::SOURCE_COUNTER, export_status.total_objects_count)
|
||||
end
|
||||
|
||||
def increment_fetched_objects_counter
|
||||
ObjectCounter.increment(tracker, ObjectCounter::FETCHED_COUNTER)
|
||||
end
|
||||
|
||||
def increment_imported_objects_counter
|
||||
ObjectCounter.increment(tracker, ObjectCounter::IMPORTED_COUNTER)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module CloudConnector
|
||||
module Config
|
||||
extend self
|
||||
|
||||
def base_url
|
||||
Gitlab.config.cloud_connector.base_url
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -239,6 +239,25 @@ module Gitlab
|
|||
end
|
||||
end
|
||||
|
||||
# Increments value of a field in a hash
|
||||
#
|
||||
# raw_key - The key of the hash to add to.
|
||||
# field - The field to increment.
|
||||
# value - The field value to add to the hash.
|
||||
# timeout - The new timeout of the key.
|
||||
def self.hash_increment(raw_key, field, value, timeout: TIMEOUT)
|
||||
return if value.to_i <= 0
|
||||
|
||||
key = cache_key_for(raw_key)
|
||||
|
||||
with_redis do |redis|
|
||||
redis.multi do |m|
|
||||
m.hincrby(key, field, value.to_i)
|
||||
m.expire(key, timeout)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Adds a value to a list.
|
||||
#
|
||||
# raw_key - The key of the list to add to.
|
||||
|
|
|
|||
|
|
@ -181,7 +181,14 @@ module Gitlab
|
|||
spend_time_message(time_spent, time_spent_date, true)
|
||||
end
|
||||
|
||||
params '<time(1h30m | -1h30m)> <date(YYYY-MM-DD)>'
|
||||
params do
|
||||
base_params = 'time(1h30m | -1h30m) <date(YYYY-MM-DD)>'
|
||||
if Feature.enabled?(:timelog_categories, quick_action_target.project)
|
||||
"#{base_params} <[timecategory:category-name]>"
|
||||
else
|
||||
base_params
|
||||
end
|
||||
end
|
||||
types Issue, MergeRequest
|
||||
condition do
|
||||
quick_action_target.supports_time_tracking? &&
|
||||
|
|
@ -190,12 +197,13 @@ module Gitlab
|
|||
parse_params do |raw_time_date|
|
||||
Gitlab::QuickActions::SpendTimeAndDateSeparator.new(raw_time_date).execute
|
||||
end
|
||||
command :spend, :spent, :spend_time do |time_spent, time_spent_date|
|
||||
command :spend, :spent, :spend_time do |time_spent, time_spent_date, category|
|
||||
if time_spent
|
||||
@updates[:spend_time] = {
|
||||
duration: time_spent,
|
||||
user_id: current_user.id,
|
||||
spent_at: time_spent_date
|
||||
spent_at: time_spent_date,
|
||||
category: category
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ module Gitlab
|
|||
# in other cases return nil
|
||||
class SpendTimeAndDateSeparator
|
||||
DATE_REGEX = %r{(\d{2,4}[/\-.]\d{1,2}[/\-.]\d{1,2})}
|
||||
CATEGORY_REGEX = %r{\[timecategory:(.*)\]}
|
||||
|
||||
def initialize(spend_command_arg)
|
||||
@spend_arg = spend_command_arg
|
||||
|
|
@ -19,20 +20,23 @@ module Gitlab
|
|||
|
||||
def execute
|
||||
return if @spend_arg.blank?
|
||||
return [get_time, DateTime.current] unless date_present?
|
||||
return unless valid_date?
|
||||
return if date_present? && !valid_date?
|
||||
|
||||
[get_time, get_date]
|
||||
[time_spent, spent_at, category]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def get_time
|
||||
def time_spent
|
||||
raw_time = @spend_arg.gsub(DATE_REGEX, '')
|
||||
raw_time = raw_time.gsub(CATEGORY_REGEX, '')
|
||||
|
||||
Gitlab::TimeTrackingFormatter.parse(raw_time)
|
||||
end
|
||||
|
||||
def get_date
|
||||
def spent_at
|
||||
return DateTime.current unless date_present?
|
||||
|
||||
string_date = @spend_arg.match(DATE_REGEX)[0]
|
||||
Date.parse(string_date)
|
||||
end
|
||||
|
|
@ -55,6 +59,12 @@ module Gitlab
|
|||
def date_past_or_today?(date)
|
||||
date&.past? || date&.today?
|
||||
end
|
||||
|
||||
def category
|
||||
return unless @spend_arg.match?(CATEGORY_REGEX)
|
||||
|
||||
@spend_arg.match(CATEGORY_REGEX)[1]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -15437,6 +15437,9 @@ msgstr ""
|
|||
msgid "DORA4Metrics|Change failure rate (percentage)"
|
||||
msgstr ""
|
||||
|
||||
msgid "DORA4Metrics|Contributor count"
|
||||
msgstr ""
|
||||
|
||||
msgid "DORA4Metrics|Critical Vulnerabilities over time"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -33505,6 +33508,9 @@ msgstr ""
|
|||
msgid "ObservabilityMetrics|is not like"
|
||||
msgstr ""
|
||||
|
||||
msgid "ObservabilityMetrics|multiple"
|
||||
msgstr ""
|
||||
|
||||
msgid "Observability|Enable"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -53962,6 +53968,9 @@ msgstr ""
|
|||
msgid "ValueStreamAnalytics|Number of issues closed by month."
|
||||
msgstr ""
|
||||
|
||||
msgid "ValueStreamAnalytics|Number of monthly unique users with contributions in the group."
|
||||
msgstr ""
|
||||
|
||||
msgid "ValueStreamAnalytics|Number of new issues created."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,10 @@ FactoryBot.define do
|
|||
sequence(:pipeline_name) { |n| "pipeline_name_#{n}" }
|
||||
sequence(:jid) { |n| "bulk_import_entity_#{n}" }
|
||||
|
||||
source_objects_count { 1 }
|
||||
fetched_objects_count { 1 }
|
||||
imported_objects_count { 1 }
|
||||
|
||||
trait :started do
|
||||
status { 1 }
|
||||
end
|
||||
|
|
|
|||
|
|
@ -23,7 +23,8 @@ RSpec.describe API::Entities::BulkImports::Entity, feature_category: :importers
|
|||
:updated_at,
|
||||
:failures,
|
||||
:migrate_projects,
|
||||
:has_failures
|
||||
:has_failures,
|
||||
:stats
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe BulkImports::Common::Pipelines::BadgesPipeline do
|
||||
RSpec.describe BulkImports::Common::Pipelines::BadgesPipeline, feature_category: :importers do
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:group) { create(:group) }
|
||||
let_it_be(:project) { create(:project) }
|
||||
|
|
@ -21,6 +21,8 @@ RSpec.describe BulkImports::Common::Pipelines::BadgesPipeline do
|
|||
allow_next_instance_of(BulkImports::Common::Extractors::RestExtractor) do |extractor|
|
||||
allow(extractor).to receive(:extract).and_return(first_page, last_page)
|
||||
end
|
||||
|
||||
allow(subject).to receive(:set_source_objects_counter)
|
||||
end
|
||||
|
||||
it 'imports a group badge' do
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ RSpec.describe BulkImports::Common::Pipelines::BoardsPipeline, feature_category:
|
|||
allow_next_instance_of(BulkImports::Common::Extractors::NdjsonExtractor) do |extractor|
|
||||
allow(extractor).to receive(:extract).and_return(BulkImports::Pipeline::ExtractedData.new(data: board_data))
|
||||
end
|
||||
allow(subject).to receive(:set_source_objects_counter)
|
||||
group.add_owner(user)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ RSpec.describe BulkImports::Common::Pipelines::LabelsPipeline, feature_category:
|
|||
let(:tmpdir) { Dir.mktmpdir }
|
||||
|
||||
before do
|
||||
allow(subject).to receive(:set_source_objects_counter)
|
||||
FileUtils.copy_file(filepath, File.join(tmpdir, 'labels.ndjson.gz'))
|
||||
group.add_owner(user)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ RSpec.describe BulkImports::Common::Pipelines::LfsObjectsPipeline, feature_categ
|
|||
File.write(lfs_json_file_path, { oid => [0, 1, 2, nil] }.to_json )
|
||||
|
||||
allow(Dir).to receive(:mktmpdir).with('bulk_imports').and_return(tmpdir)
|
||||
allow(pipeline).to receive(:set_source_objects_counter)
|
||||
end
|
||||
|
||||
after do
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe BulkImports::Common::Pipelines::MembersPipeline do
|
||||
RSpec.describe BulkImports::Common::Pipelines::MembersPipeline, feature_category: :importers do
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:bulk_import) { create(:bulk_import, user: user) }
|
||||
let_it_be(:member_user1) { create(:user, email: 'email1@email.com') }
|
||||
|
|
@ -25,6 +25,10 @@ RSpec.describe BulkImports::Common::Pipelines::MembersPipeline do
|
|||
|
||||
subject(:pipeline) { described_class.new(context) }
|
||||
|
||||
before do
|
||||
allow(pipeline).to receive(:set_source_objects_counter)
|
||||
end
|
||||
|
||||
def extracted_data(email:, has_next_page: false)
|
||||
data = {
|
||||
'created_at' => '2020-01-01T00:00:00Z',
|
||||
|
|
|
|||
|
|
@ -48,6 +48,8 @@ RSpec.describe BulkImports::Common::Pipelines::MilestonesPipeline, feature_categ
|
|||
allow_next_instance_of(BulkImports::Common::Extractors::NdjsonExtractor) do |extractor|
|
||||
allow(extractor).to receive(:extract).and_return(BulkImports::Pipeline::ExtractedData.new(data: exported_milestones))
|
||||
end
|
||||
|
||||
allow(subject).to receive(:set_source_objects_counter)
|
||||
end
|
||||
|
||||
subject { described_class.new(context) }
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ RSpec.describe BulkImports::Common::Pipelines::UploadsPipeline, feature_category
|
|||
|
||||
FileUtils.mkdir_p(uploads_dir_path)
|
||||
FileUtils.touch(upload_file_path)
|
||||
|
||||
allow(pipeline).to receive(:set_source_objects_counter)
|
||||
end
|
||||
|
||||
after do
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe BulkImports::Groups::Pipelines::GroupAttributesPipeline do
|
||||
RSpec.describe BulkImports::Groups::Pipelines::GroupAttributesPipeline, feature_category: :importers do
|
||||
subject(:pipeline) { described_class.new(context) }
|
||||
|
||||
let_it_be(:user) { create(:user) }
|
||||
|
|
@ -35,6 +35,8 @@ RSpec.describe BulkImports::Groups::Pipelines::GroupAttributesPipeline do
|
|||
BulkImports::Pipeline::ExtractedData.new(data: group_attributes)
|
||||
)
|
||||
end
|
||||
|
||||
allow(pipeline).to receive(:set_source_objects_counter)
|
||||
end
|
||||
|
||||
it 'imports allowed group attributes' do
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe BulkImports::Groups::Pipelines::GroupPipeline do
|
||||
RSpec.describe BulkImports::Groups::Pipelines::GroupPipeline, feature_category: :importers do
|
||||
describe '#run', :clean_gitlab_redis_cache do
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:parent) { create(:group) }
|
||||
|
|
@ -42,6 +42,8 @@ RSpec.describe BulkImports::Groups::Pipelines::GroupPipeline do
|
|||
allow(extractor).to receive(:extract).and_return(BulkImports::Pipeline::ExtractedData.new(data: group_data))
|
||||
end
|
||||
|
||||
allow(subject).to receive(:set_source_objects_counter)
|
||||
|
||||
parent.add_owner(user)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe BulkImports::Groups::Pipelines::NamespaceSettingsPipeline do
|
||||
RSpec.describe BulkImports::Groups::Pipelines::NamespaceSettingsPipeline, feature_category: :importers do
|
||||
subject(:pipeline) { described_class.new(context) }
|
||||
|
||||
let_it_be(:user) { create(:user) }
|
||||
|
|
@ -14,6 +14,8 @@ RSpec.describe BulkImports::Groups::Pipelines::NamespaceSettingsPipeline do
|
|||
|
||||
before do
|
||||
group.add_owner(user)
|
||||
|
||||
allow(pipeline).to receive(:set_source_objects_counter)
|
||||
end
|
||||
|
||||
describe '#run' do
|
||||
|
|
|
|||
|
|
@ -34,6 +34,8 @@ RSpec.describe BulkImports::Groups::Pipelines::ProjectEntitiesPipeline, feature_
|
|||
allow(extractor).to receive(:extract).and_return(extracted_data)
|
||||
end
|
||||
|
||||
allow(subject).to receive(:set_source_objects_counter)
|
||||
|
||||
destination_group.add_owner(user)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe BulkImports::Groups::Pipelines::SubgroupEntitiesPipeline do
|
||||
RSpec.describe BulkImports::Groups::Pipelines::SubgroupEntitiesPipeline, feature_category: :importers do
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:group) { create(:group, path: 'group') }
|
||||
let_it_be(:parent) { create(:group, name: 'Imported Group', path: 'imported-group') }
|
||||
|
|
@ -25,6 +25,8 @@ RSpec.describe BulkImports::Groups::Pipelines::SubgroupEntitiesPipeline do
|
|||
allow(extractor).to receive(:extract).and_return(extracted_data)
|
||||
end
|
||||
|
||||
allow(subject).to receive(:set_source_objects_counter)
|
||||
|
||||
parent.add_owner(user)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,132 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe BulkImports::ObjectCounter, :clean_gitlab_redis_cache, feature_category: :importers do
|
||||
let_it_be(:tracker) { build(:bulk_import_tracker, id: non_existing_record_id) }
|
||||
let_it_be(:cache_key) { "bulk_imports/object_counter/#{tracker.id}" }
|
||||
|
||||
describe '.increment' do
|
||||
it 'increments counter by 1' do
|
||||
expect(Gitlab::Cache::Import::Caching)
|
||||
.to receive(:hash_increment)
|
||||
.with(cache_key, described_class::SOURCE_COUNTER, 1)
|
||||
|
||||
described_class.increment(tracker, described_class::SOURCE_COUNTER)
|
||||
end
|
||||
|
||||
it 'increments counter by given value' do
|
||||
expect(Gitlab::Cache::Import::Caching)
|
||||
.to receive(:hash_increment)
|
||||
.with(cache_key, described_class::SOURCE_COUNTER, 10)
|
||||
|
||||
described_class.increment(tracker, described_class::SOURCE_COUNTER, 10)
|
||||
end
|
||||
|
||||
context 'when value is not an integer' do
|
||||
it 'does not increment counter' do
|
||||
expect(Gitlab::Cache::Import::Caching).not_to receive(:hash_increment)
|
||||
|
||||
described_class.increment(tracker, described_class::SOURCE_COUNTER, 'foo')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when value is less than 1' do
|
||||
it 'does not increment counter' do
|
||||
expect(Gitlab::Cache::Import::Caching).not_to receive(:hash_increment)
|
||||
|
||||
described_class.increment(tracker, described_class::SOURCE_COUNTER, 0)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when counter type is invalid' do
|
||||
it 'does not increment counter' do
|
||||
expect(Gitlab::Cache::Import::Caching).not_to receive(:hash_increment)
|
||||
|
||||
described_class.increment(tracker, 'foo')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.set' do
|
||||
it 'sets counter to given value' do
|
||||
expect(Gitlab::Cache::Import::Caching).to receive(:hash_add).with(cache_key, described_class::SOURCE_COUNTER, 10)
|
||||
|
||||
described_class.set(tracker, described_class::SOURCE_COUNTER, 10)
|
||||
end
|
||||
|
||||
context 'when value is not an integer' do
|
||||
it 'does not set counter' do
|
||||
expect(Gitlab::Cache::Import::Caching).not_to receive(:hash_add)
|
||||
|
||||
described_class.set(tracker, described_class::SOURCE_COUNTER, 'foo')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when value is less than 1' do
|
||||
it 'does not set counter' do
|
||||
expect(Gitlab::Cache::Import::Caching).not_to receive(:hash_add)
|
||||
|
||||
described_class.set(tracker, described_class::SOURCE_COUNTER, 0)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when counter type is invalid' do
|
||||
it 'does not set counter' do
|
||||
expect(Gitlab::Cache::Import::Caching).not_to receive(:hash_add)
|
||||
|
||||
described_class.set(tracker, 'foo')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.summary' do
|
||||
it 'returns symbolized hash' do
|
||||
expect(Gitlab::Cache::Import::Caching)
|
||||
.to receive(:values_from_hash)
|
||||
.with(cache_key).and_return({ 'source' => 10 })
|
||||
|
||||
expect(described_class.summary(tracker)).to eq(source: 10, fetched: 0, imported: 0)
|
||||
end
|
||||
|
||||
context 'when hash is empty' do
|
||||
it 'returns nil' do
|
||||
expect(Gitlab::Cache::Import::Caching).to receive(:values_from_hash).with(cache_key).and_return({})
|
||||
|
||||
expect(described_class.summary(tracker)).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when return value is not a hash' do
|
||||
it 'returns nil' do
|
||||
expect(Gitlab::Cache::Import::Caching).to receive(:values_from_hash).with(cache_key).and_return('foo')
|
||||
|
||||
expect(described_class.summary(tracker)).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.persist!' do
|
||||
it 'updates tracker with summary' do
|
||||
tracker = create(
|
||||
:bulk_import_tracker,
|
||||
source_objects_count: 0,
|
||||
fetched_objects_count: 0,
|
||||
imported_objects_count: 0
|
||||
)
|
||||
|
||||
expect(Gitlab::Cache::Import::Caching)
|
||||
.to receive(:values_from_hash)
|
||||
.with("bulk_imports/object_counter/#{tracker.id}")
|
||||
.and_return('source' => 10, 'fetched' => 20, 'imported' => 30)
|
||||
|
||||
described_class.persist!(tracker)
|
||||
|
||||
tracker.reload
|
||||
|
||||
expect(tracker.source_objects_count).to eq(10)
|
||||
expect(tracker.fetched_objects_count).to eq(20)
|
||||
expect(tracker.imported_objects_count).to eq(30)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -41,6 +41,12 @@ RSpec.describe BulkImports::Pipeline::Runner, feature_category: :importers do
|
|||
end
|
||||
|
||||
stub_const('BulkImports::MyPipeline', pipeline)
|
||||
|
||||
allow_next_instance_of(BulkImports::ExportStatus) do |export_status|
|
||||
allow(export_status).to receive(:total_objects_count).and_return(1)
|
||||
end
|
||||
|
||||
allow(tracker).to receive_message_chain(:pipeline_class, :relation).and_return('relation')
|
||||
end
|
||||
|
||||
let_it_be(:bulk_import) { create(:bulk_import) }
|
||||
|
|
@ -433,6 +439,35 @@ RSpec.describe BulkImports::Pipeline::Runner, feature_category: :importers do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'object counting' do
|
||||
it 'increments object counters' do
|
||||
allow_next_instance_of(BulkImports::Extractor) do |extractor|
|
||||
allow(extractor).to receive(:extract).with(context).and_return(extracted_data)
|
||||
end
|
||||
|
||||
allow_next_instance_of(BulkImports::Transformer) do |transformer|
|
||||
allow(transformer)
|
||||
.to receive(:transform)
|
||||
.with(context, extracted_data.data.first)
|
||||
.and_return(extracted_data.data.first)
|
||||
end
|
||||
|
||||
allow_next_instance_of(BulkImports::Loader) do |loader|
|
||||
expect(loader).to receive(:load).with(context, extracted_data.data.first)
|
||||
end
|
||||
|
||||
expect(BulkImports::ObjectCounter).to receive(:set).with(tracker, :source, 1)
|
||||
expect(BulkImports::ObjectCounter).to receive(:increment).with(tracker, :fetched)
|
||||
expect(BulkImports::ObjectCounter).to receive(:increment).with(tracker, :imported)
|
||||
|
||||
subject.run
|
||||
|
||||
expect(tracker.source_objects_count).to eq(1)
|
||||
expect(tracker.fetched_objects_count).to eq(1)
|
||||
expect(tracker.imported_objects_count).to eq(1)
|
||||
end
|
||||
end
|
||||
|
||||
def log_params(context, extra = {})
|
||||
{
|
||||
bulk_import_id: context.bulk_import_id,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe BulkImports::Projects::Pipelines::AutoDevopsPipeline do
|
||||
RSpec.describe BulkImports::Projects::Pipelines::AutoDevopsPipeline, feature_category: :importers do
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:group) { create(:group) }
|
||||
let_it_be(:project) { create(:project, group: group) }
|
||||
|
|
@ -41,6 +41,8 @@ RSpec.describe BulkImports::Projects::Pipelines::AutoDevopsPipeline do
|
|||
allow(extractor).to receive(:extract).and_return(BulkImports::Pipeline::ExtractedData.new(data: [auto_devops]))
|
||||
end
|
||||
|
||||
allow(pipeline).to receive(:set_source_objects_counter)
|
||||
|
||||
pipeline.run
|
||||
|
||||
expect(project.auto_devops.enabled).to be_truthy
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe BulkImports::Projects::Pipelines::CiPipelinesPipeline do
|
||||
RSpec.describe BulkImports::Projects::Pipelines::CiPipelinesPipeline, feature_category: :importers do
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:group) { create(:group) }
|
||||
let_it_be(:project) { create(:project, group: group) }
|
||||
|
|
@ -43,6 +43,10 @@ RSpec.describe BulkImports::Projects::Pipelines::CiPipelinesPipeline do
|
|||
|
||||
subject(:pipeline) { described_class.new(context) }
|
||||
|
||||
before do
|
||||
allow(pipeline).to receive(:set_source_objects_counter)
|
||||
end
|
||||
|
||||
describe '#run', :clean_gitlab_redis_cache do
|
||||
before do
|
||||
group.add_owner(user)
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ RSpec.describe BulkImports::Projects::Pipelines::CommitNotesPipeline, feature_ca
|
|||
subject(:pipeline) { described_class.new(context) }
|
||||
|
||||
describe '#run' do
|
||||
before do
|
||||
it 'imports ci pipeline notes into destination project' do
|
||||
group.add_owner(user)
|
||||
|
||||
allow_next_instance_of(BulkImports::Common::Extractors::NdjsonExtractor) do |extractor|
|
||||
|
|
@ -60,9 +60,9 @@ RSpec.describe BulkImports::Projects::Pipelines::CommitNotesPipeline, feature_ca
|
|||
BulkImports::Pipeline::ExtractedData.new(data: [ci_pipeline_note])
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
it 'imports ci pipeline notes into destination project' do
|
||||
allow(pipeline).to receive(:set_source_objects_counter)
|
||||
|
||||
expect { pipeline.run }.to change { project.notes.for_commit_id("sha-notes").count }.from(0).to(1)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -30,6 +30,8 @@ RSpec.describe BulkImports::Projects::Pipelines::ContainerExpirationPolicyPipeli
|
|||
allow(extractor).to receive(:extract).and_return(BulkImports::Pipeline::ExtractedData.new(data: [[policy, 0]]))
|
||||
end
|
||||
|
||||
allow(pipeline).to receive(:set_source_objects_counter)
|
||||
|
||||
pipeline.run
|
||||
|
||||
policy.each_pair do |key, value|
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ RSpec.describe BulkImports::Projects::Pipelines::DesignBundlePipeline, feature_c
|
|||
|
||||
allow(portable).to receive(:lfs_enabled?).and_return(true)
|
||||
allow(Dir).to receive(:mktmpdir).with('bulk_imports').and_return(tmpdir)
|
||||
allow(pipeline).to receive(:set_source_objects_counter)
|
||||
end
|
||||
|
||||
after do
|
||||
|
|
|
|||
|
|
@ -35,6 +35,8 @@ RSpec.describe BulkImports::Projects::Pipelines::ExternalPullRequestsPipeline, f
|
|||
allow(extractor).to receive(:extract).and_return(BulkImports::Pipeline::ExtractedData.new(data: [[external_pull_request, 0]]))
|
||||
end
|
||||
|
||||
allow(pipeline).to receive(:set_source_objects_counter)
|
||||
|
||||
pipeline.run
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe BulkImports::Projects::Pipelines::IssuesPipeline do
|
||||
RSpec.describe BulkImports::Projects::Pipelines::IssuesPipeline, feature_category: :importers do
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:group) { create(:group) }
|
||||
let_it_be(:project) { create(:project, group: group) }
|
||||
|
|
@ -45,6 +45,8 @@ RSpec.describe BulkImports::Projects::Pipelines::IssuesPipeline do
|
|||
allow(extractor).to receive(:extract).and_return(BulkImports::Pipeline::ExtractedData.new(data: [issue_with_index]))
|
||||
end
|
||||
|
||||
allow(pipeline).to receive(:set_source_objects_counter)
|
||||
|
||||
pipeline.run
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -101,6 +101,7 @@ RSpec.describe BulkImports::Projects::Pipelines::MergeRequestsPipeline, feature_
|
|||
allow(project.repository).to receive(:create_branch)
|
||||
|
||||
allow(::Projects::ImportExport::AfterImportMergeRequestsWorker).to receive(:perform_async)
|
||||
allow(pipeline).to receive(:set_source_objects_counter)
|
||||
|
||||
pipeline.run
|
||||
end
|
||||
|
|
|
|||
|
|
@ -43,6 +43,8 @@ RSpec.describe BulkImports::Projects::Pipelines::PipelineSchedulesPipeline, :cle
|
|||
allow(extractor).to receive(:extract).and_return(BulkImports::Pipeline::ExtractedData.new(data: [schedule]))
|
||||
end
|
||||
|
||||
allow(pipeline).to receive(:set_source_objects_counter)
|
||||
|
||||
pipeline.run
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe BulkImports::Projects::Pipelines::ProjectAttributesPipeline, :with_license do
|
||||
RSpec.describe BulkImports::Projects::Pipelines::ProjectAttributesPipeline, :with_license, feature_category: :importers do
|
||||
let_it_be(:project) { create(:project) }
|
||||
let_it_be(:bulk_import) { create(:bulk_import) }
|
||||
let_it_be(:entity) { create(:bulk_import_entity, :project_entity, project: project, bulk_import: bulk_import) }
|
||||
|
|
@ -62,6 +62,8 @@ RSpec.describe BulkImports::Projects::Pipelines::ProjectAttributesPipeline, :wit
|
|||
)
|
||||
end
|
||||
|
||||
allow(pipeline).to receive(:set_source_objects_counter)
|
||||
|
||||
pipeline.run
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe BulkImports::Projects::Pipelines::ProjectFeaturePipeline do
|
||||
RSpec.describe BulkImports::Projects::Pipelines::ProjectFeaturePipeline, feature_category: :importers do
|
||||
let_it_be(:project) { create(:project) }
|
||||
let_it_be(:entity) { create(:bulk_import_entity, :project_entity, project: project) }
|
||||
let_it_be(:tracker) { create(:bulk_import_tracker, entity: entity) }
|
||||
|
|
@ -35,6 +35,8 @@ RSpec.describe BulkImports::Projects::Pipelines::ProjectFeaturePipeline do
|
|||
allow(extractor).to receive(:extract).and_return(BulkImports::Pipeline::ExtractedData.new(data: [[project_feature, 0]]))
|
||||
end
|
||||
|
||||
allow(pipeline).to receive(:set_source_objects_counter)
|
||||
|
||||
pipeline.run
|
||||
|
||||
project_feature.each_pair do |key, value|
|
||||
|
|
|
|||
|
|
@ -36,6 +36,8 @@ RSpec.describe BulkImports::Projects::Pipelines::ProjectPipeline, feature_catego
|
|||
allow(extractor).to receive(:extract).and_return(BulkImports::Pipeline::ExtractedData.new(data: project_data))
|
||||
end
|
||||
|
||||
allow(project_pipeline).to receive(:set_source_objects_counter)
|
||||
|
||||
group.add_owner(user)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe BulkImports::Projects::Pipelines::ProtectedBranchesPipeline do
|
||||
RSpec.describe BulkImports::Projects::Pipelines::ProtectedBranchesPipeline, feature_category: :importers do
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:project) { create(:project) }
|
||||
let_it_be(:bulk_import) { create(:bulk_import, user: user) }
|
||||
|
|
@ -39,6 +39,8 @@ RSpec.describe BulkImports::Projects::Pipelines::ProtectedBranchesPipeline do
|
|||
allow(extractor).to receive(:extract).and_return(BulkImports::Pipeline::ExtractedData.new(data: [protected_branch, 0]))
|
||||
end
|
||||
|
||||
allow(pipeline).to receive(:set_source_objects_counter)
|
||||
|
||||
pipeline.run
|
||||
|
||||
imported_protected_branch = project.protected_branches.last
|
||||
|
|
|
|||
|
|
@ -37,6 +37,10 @@ RSpec.describe BulkImports::Projects::Pipelines::ReferencesPipeline, feature_cat
|
|||
|
||||
subject(:pipeline) { described_class.new(context) }
|
||||
|
||||
before do
|
||||
allow(subject).to receive(:set_source_objects_counter)
|
||||
end
|
||||
|
||||
describe '#run' do
|
||||
it "enqueues TransformReferencesWorker for the project's issues, mrs and their notes" do
|
||||
expect(BulkImports::TransformReferencesWorker).to receive(:perform_in)
|
||||
|
|
|
|||
|
|
@ -46,6 +46,8 @@ RSpec.describe BulkImports::Projects::Pipelines::ReleasesPipeline, feature_categ
|
|||
allow_next_instance_of(BulkImports::Common::Extractors::NdjsonExtractor) do |extractor|
|
||||
allow(extractor).to receive(:extract).and_return(BulkImports::Pipeline::ExtractedData.new(data: [with_index]))
|
||||
end
|
||||
|
||||
allow(pipeline).to receive(:set_source_objects_counter)
|
||||
end
|
||||
|
||||
it 'imports release into destination project' do
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ RSpec.describe BulkImports::Projects::Pipelines::RepositoryBundlePipeline, featu
|
|||
source.repository.bundle_to_disk(bundle_path)
|
||||
|
||||
allow(Dir).to receive(:mktmpdir).with('bulk_imports').and_return(tmpdir)
|
||||
allow(pipeline).to receive(:set_source_objects_counter)
|
||||
end
|
||||
|
||||
after do
|
||||
|
|
|
|||
|
|
@ -32,6 +32,8 @@ RSpec.describe BulkImports::Projects::Pipelines::RepositoryPipeline, feature_cat
|
|||
allow_next_instance_of(BulkImports::Common::Extractors::GraphqlExtractor) do |extractor|
|
||||
allow(extractor).to receive(:extract).and_return(extracted_data)
|
||||
end
|
||||
|
||||
allow(pipeline).to receive(:set_source_objects_counter)
|
||||
end
|
||||
|
||||
describe '#run' do
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe BulkImports::Projects::Pipelines::ServiceDeskSettingPipeline do
|
||||
RSpec.describe BulkImports::Projects::Pipelines::ServiceDeskSettingPipeline, feature_category: :importers do
|
||||
let_it_be(:project) { create(:project) }
|
||||
let_it_be(:entity) { create(:bulk_import_entity, :project_entity, project: project) }
|
||||
let_it_be(:tracker) { create(:bulk_import_tracker, entity: entity) }
|
||||
|
|
@ -17,6 +17,8 @@ RSpec.describe BulkImports::Projects::Pipelines::ServiceDeskSettingPipeline do
|
|||
allow(extractor).to receive(:extract).and_return(BulkImports::Pipeline::ExtractedData.new(data: [[setting, 0]]))
|
||||
end
|
||||
|
||||
allow(pipeline).to receive(:set_source_objects_counter)
|
||||
|
||||
pipeline.run
|
||||
|
||||
setting.each_pair do |key, value|
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe BulkImports::Projects::Pipelines::SnippetsPipeline do
|
||||
RSpec.describe BulkImports::Projects::Pipelines::SnippetsPipeline, feature_category: :importers do
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:group) { create(:group) }
|
||||
let_it_be(:project) { create(:project, group: group) }
|
||||
|
|
@ -49,6 +49,8 @@ RSpec.describe BulkImports::Projects::Pipelines::SnippetsPipeline do
|
|||
allow(extractor).to receive(:extract).and_return(BulkImports::Pipeline::ExtractedData.new(data: [snippet_with_index]))
|
||||
end
|
||||
|
||||
allow(pipeline).to receive(:set_source_objects_counter)
|
||||
|
||||
pipeline.run
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -45,6 +45,10 @@ RSpec.describe BulkImports::Projects::Pipelines::SnippetsRepositoryPipeline, fea
|
|||
|
||||
let(:extracted_data) { BulkImports::Pipeline::ExtractedData.new(data: data, page_info: page_info) }
|
||||
|
||||
before do
|
||||
allow(pipeline).to receive(:set_source_objects_counter)
|
||||
end
|
||||
|
||||
describe 'extractor' do
|
||||
it 'is a GraphqlExtractor with Graphql::GetSnippetRepositoryQuery' do
|
||||
expect(described_class.get_extractor).to eq(
|
||||
|
|
|
|||
|
|
@ -171,6 +171,40 @@ RSpec.describe Gitlab::Cache::Import::Caching, :clean_gitlab_redis_cache, :clean
|
|||
end
|
||||
end
|
||||
|
||||
describe '.hash_increment' do
|
||||
it 'increments a value in a hash' do
|
||||
described_class.hash_increment('foo', 'field', 1)
|
||||
described_class.hash_increment('foo', 'field', 5)
|
||||
|
||||
key = described_class.cache_key_for('foo')
|
||||
values = Gitlab::Redis::Cache.with { |r| r.hgetall(key) }
|
||||
|
||||
expect(values).to eq({ 'field' => '6' })
|
||||
end
|
||||
|
||||
context 'when the value is not an integer' do
|
||||
it 'returns' do
|
||||
described_class.hash_increment('another-foo', 'another-field', 'not-an-integer')
|
||||
|
||||
key = described_class.cache_key_for('foo')
|
||||
values = Gitlab::Redis::Cache.with { |r| r.hgetall(key) }
|
||||
|
||||
expect(values).to eq({})
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the value is less than 0' do
|
||||
it 'returns' do
|
||||
described_class.hash_increment('another-foo', 'another-field', -5)
|
||||
|
||||
key = described_class.cache_key_for('foo')
|
||||
values = Gitlab::Redis::Cache.with { |r| r.hgetall(key) }
|
||||
|
||||
expect(values).to eq({})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.write_multiple' do
|
||||
it 'sets multiple keys when key_prefix not set' do
|
||||
mapping = { 'foo' => 10, 'bar' => 20 }
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::QuickActions::SpendTimeAndDateSeparator do
|
||||
RSpec.describe Gitlab::QuickActions::SpendTimeAndDateSeparator, feature_category: :team_planning do
|
||||
subject { described_class }
|
||||
|
||||
shared_examples 'arg line with invalid parameters' do
|
||||
|
|
@ -51,23 +51,38 @@ RSpec.describe Gitlab::QuickActions::SpendTimeAndDateSeparator do
|
|||
end
|
||||
end
|
||||
|
||||
context 'only time present in arg line' do
|
||||
it_behaves_like 'arg line with valid parameters' do
|
||||
let(:valid_arg) { '2m 3m 5m 1h' }
|
||||
let(:time) { Gitlab::TimeTrackingFormatter.parse(valid_arg) }
|
||||
let(:date) { DateTime.current }
|
||||
let(:expected_response) { [time, date] }
|
||||
context 'time present in arg line' do
|
||||
let(:time_part) { '2m 3m 5m 1h' }
|
||||
let(:valid_arg) { time_part }
|
||||
let(:time) { Gitlab::TimeTrackingFormatter.parse(time_part) }
|
||||
let(:date) { DateTime.current }
|
||||
let(:expected_response) { [time, date, nil] }
|
||||
|
||||
it_behaves_like 'arg line with valid parameters'
|
||||
|
||||
context 'timecategory present in arg line' do
|
||||
let(:valid_arg) { "#{time_part} [timecategory:dev]" }
|
||||
let(:expected_response) { [time, date, 'dev'] }
|
||||
|
||||
it_behaves_like 'arg line with valid parameters'
|
||||
end
|
||||
end
|
||||
|
||||
context 'simple time with date in arg line' do
|
||||
it_behaves_like 'arg line with valid parameters' do
|
||||
let(:raw_time) { '10m' }
|
||||
let(:raw_date) { '2016-02-02' }
|
||||
let(:valid_arg) { "#{raw_time} #{raw_date}" }
|
||||
let(:date) { Date.parse(raw_date) }
|
||||
let(:time) { Gitlab::TimeTrackingFormatter.parse(raw_time) }
|
||||
let(:expected_response) { [time, date] }
|
||||
let(:raw_time) { '10m' }
|
||||
let(:raw_date) { '2016-02-02' }
|
||||
let(:valid_arg) { "#{raw_time} #{raw_date}" }
|
||||
let(:date) { Date.parse(raw_date) }
|
||||
let(:time) { Gitlab::TimeTrackingFormatter.parse(raw_time) }
|
||||
let(:expected_response) { [time, date, nil] }
|
||||
|
||||
it_behaves_like 'arg line with valid parameters'
|
||||
|
||||
context 'timecategory present in arg line' do
|
||||
let(:valid_arg) { "#{raw_time} #{raw_date} [timecategory:support]" }
|
||||
let(:expected_response) { [time, date, 'support'] }
|
||||
|
||||
it_behaves_like 'arg line with valid parameters'
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -78,7 +93,7 @@ RSpec.describe Gitlab::QuickActions::SpendTimeAndDateSeparator do
|
|||
let(:valid_arg) { "#{raw_time} #{raw_date}" }
|
||||
let(:date) { Date.parse(raw_date) }
|
||||
let(:time) { Gitlab::TimeTrackingFormatter.parse(raw_time) }
|
||||
let(:expected_response) { [time, date] }
|
||||
let(:expected_response) { [time, date, nil] }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -487,4 +487,71 @@ RSpec.describe BulkImports::Entity, type: :model, feature_category: :importers d
|
|||
expect(subject.source_version).to eq(subject.bulk_import.source_version_info)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#checksums' do
|
||||
let(:entity) { create(:bulk_import_entity) }
|
||||
|
||||
it 'returns checksums for all imported relations' do
|
||||
create(:bulk_import_tracker,
|
||||
entity: entity,
|
||||
relation: 'BulkImports::Common::Pipelines::MilestonesPipeline',
|
||||
source_objects_count: 7,
|
||||
fetched_objects_count: 6,
|
||||
imported_objects_count: 5
|
||||
)
|
||||
|
||||
create(:bulk_import_tracker,
|
||||
entity: entity,
|
||||
relation: 'BulkImports::Common::Pipelines::LabelsPipeline',
|
||||
source_objects_count: 10,
|
||||
fetched_objects_count: 9,
|
||||
imported_objects_count: 8
|
||||
)
|
||||
|
||||
expect(entity.checksums).to eq(
|
||||
{
|
||||
milestones: {
|
||||
source: 7,
|
||||
fetched: 6,
|
||||
imported: 5
|
||||
},
|
||||
labels: {
|
||||
source: 10,
|
||||
fetched: 9,
|
||||
imported: 8
|
||||
}
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
context 'when tracker should not be included' do
|
||||
let(:tracker) { create(:bulk_import_tracker, entity: entity, relation: 'BulkImports::Common::Pipelines::MilestonesPipeline') }
|
||||
|
||||
context 'when tracker is for a file extraction pipeline' do
|
||||
it 'does not include the tracker' do
|
||||
expect(entity.checksums).to eq({})
|
||||
end
|
||||
end
|
||||
|
||||
context 'when tracker is skipped' do
|
||||
it 'does not include the tracker' do
|
||||
tracker.skip!
|
||||
|
||||
expect(entity.checksums).to eq({})
|
||||
end
|
||||
end
|
||||
|
||||
context 'when tracker checksums are zeros' do
|
||||
it 'does not include the tracker' do
|
||||
tracker.update!(
|
||||
source_objects_count: 0,
|
||||
fetched_objects_count: 0,
|
||||
imported_objects_count: 0
|
||||
)
|
||||
|
||||
expect(entity.checksums).to eq({})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -389,4 +389,12 @@ RSpec.describe BulkImports::ExportStatus, :clean_gitlab_redis_cache, feature_cat
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#total_objects_count' do
|
||||
let(:status) { BulkImports::Export::FINISHED }
|
||||
|
||||
it 'returns total objects count' do
|
||||
expect(subject.total_objects_count).to eq(1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -110,4 +110,100 @@ RSpec.describe BulkImports::Tracker, type: :model, feature_category: :importers
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#checksums' do
|
||||
let(:tracker) { create(:bulk_import_tracker) }
|
||||
let(:checksums) { { source: 1, fetched: 1, imported: 1 } }
|
||||
|
||||
before do
|
||||
allow(tracker).to receive(:file_extraction_pipeline?).and_return(true)
|
||||
allow(tracker).to receive_message_chain(:pipeline_class, :relation, :to_sym).and_return(:labels)
|
||||
end
|
||||
|
||||
context 'when checksums are cached' do
|
||||
it 'returns the cached checksums' do
|
||||
allow(BulkImports::ObjectCounter).to receive(:summary).and_return(checksums)
|
||||
|
||||
expect(tracker.checksums).to eq({ labels: checksums })
|
||||
end
|
||||
end
|
||||
|
||||
context 'when checksums are persisted' do
|
||||
it 'returns the persisted checksums' do
|
||||
allow(BulkImports::ObjectCounter).to receive(:summary).and_return(nil)
|
||||
|
||||
tracker.update!(
|
||||
source_objects_count: checksums[:source],
|
||||
fetched_objects_count: checksums[:fetched],
|
||||
imported_objects_count: checksums[:imported]
|
||||
)
|
||||
|
||||
expect(tracker.checksums).to eq({ labels: checksums })
|
||||
end
|
||||
end
|
||||
|
||||
context 'when pipeline is not a file extraction pipeline' do
|
||||
it 'returns nil' do
|
||||
allow(tracker).to receive(:file_extraction_pipeline?).and_return(false)
|
||||
|
||||
expect(tracker.checksums).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#checksums_empty?' do
|
||||
let(:tracker) { create(:bulk_import_tracker) }
|
||||
|
||||
before do
|
||||
allow(tracker).to receive_message_chain(:pipeline_class, :relation, :to_sym).and_return(:labels)
|
||||
end
|
||||
|
||||
context 'when checksums are missing' do
|
||||
it 'returns true' do
|
||||
allow(tracker).to receive(:checksums).and_return(nil)
|
||||
|
||||
expect(tracker.checksums_empty?).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when checksums are present' do
|
||||
it 'returns false' do
|
||||
allow(tracker)
|
||||
.to receive(:checksums)
|
||||
.and_return({ labels: { source: 1, fetched: 1, imported: 1 } })
|
||||
|
||||
expect(tracker.checksums_empty?).to eq(false)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when checksums are all zeros' do
|
||||
it 'returns true' do
|
||||
allow(tracker)
|
||||
.to receive(:checksums)
|
||||
.and_return({ labels: { source: 0, fetched: 0, imported: 0 } })
|
||||
|
||||
expect(tracker.checksums_empty?).to eq(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'checksums persistence' do
|
||||
let(:tracker) { create(:bulk_import_tracker, :started) }
|
||||
|
||||
context 'when transitioned to finished' do
|
||||
it 'persists the checksums' do
|
||||
expect(BulkImports::ObjectCounter).to receive(:persist!).with(tracker)
|
||||
|
||||
tracker.finish!
|
||||
end
|
||||
end
|
||||
|
||||
context 'when transitioned to failed' do
|
||||
it 'persists the checksums' do
|
||||
expect(BulkImports::ObjectCounter).to receive(:persist!).with(tracker)
|
||||
|
||||
tracker.fail_op!
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -10,6 +10,38 @@ RSpec.describe API::BulkImports, feature_category: :importers do
|
|||
let_it_be(:entity_2) { create(:bulk_import_entity, bulk_import: import_1) }
|
||||
let_it_be(:entity_3) { create(:bulk_import_entity, bulk_import: import_2) }
|
||||
let_it_be(:failure_3) { create(:bulk_import_failure, entity: entity_3) }
|
||||
let_it_be(:tracker_1) do
|
||||
create(
|
||||
:bulk_import_tracker,
|
||||
entity: entity_1,
|
||||
relation: 'BulkImports::Common::Pipelines::LabelsPipeline',
|
||||
source_objects_count: 3,
|
||||
fetched_objects_count: 2,
|
||||
imported_objects_count: 1
|
||||
)
|
||||
end
|
||||
|
||||
let_it_be(:tracker_2) do
|
||||
create(
|
||||
:bulk_import_tracker,
|
||||
entity: entity_2,
|
||||
relation: 'BulkImports::Common::Pipelines::MilestonesPipeline',
|
||||
source_objects_count: 5,
|
||||
fetched_objects_count: 4,
|
||||
imported_objects_count: 3
|
||||
)
|
||||
end
|
||||
|
||||
let_it_be(:tracker_3) do
|
||||
create(
|
||||
:bulk_import_tracker,
|
||||
entity: entity_3,
|
||||
relation: 'BulkImports::Common::Pipelines::BoardsPipeline',
|
||||
source_objects_count: 10,
|
||||
fetched_objects_count: 9,
|
||||
imported_objects_count: 8
|
||||
)
|
||||
end
|
||||
|
||||
before do
|
||||
stub_application_setting(bulk_import_enabled: true)
|
||||
|
|
@ -370,6 +402,16 @@ RSpec.describe API::BulkImports, feature_category: :importers do
|
|||
expect(json_response.pluck('id')).to contain_exactly(entity_1.id, entity_2.id, entity_3.id)
|
||||
end
|
||||
|
||||
it 'includes entity stats' do
|
||||
request
|
||||
|
||||
expect(json_response.pluck('stats')).to contain_exactly(
|
||||
{ 'labels' => { 'source' => 3, 'fetched' => 2, 'imported' => 1 } },
|
||||
{ 'milestones' => { 'source' => 5, 'fetched' => 4, 'imported' => 3 } },
|
||||
{ 'boards' => { 'source' => 10, 'fetched' => 9, 'imported' => 8 } }
|
||||
)
|
||||
end
|
||||
|
||||
it_behaves_like 'disabled feature'
|
||||
end
|
||||
|
||||
|
|
@ -397,6 +439,14 @@ RSpec.describe API::BulkImports, feature_category: :importers do
|
|||
expect(json_response.first['failures'].first['exception_message']).to eq(failure_3.exception_message)
|
||||
end
|
||||
|
||||
it 'includes entity stats' do
|
||||
request
|
||||
|
||||
expect(json_response.pluck('stats')).to contain_exactly(
|
||||
{ 'boards' => { 'source' => 10, 'fetched' => 9, 'imported' => 8 } }
|
||||
)
|
||||
end
|
||||
|
||||
it_behaves_like 'disabled feature'
|
||||
end
|
||||
|
||||
|
|
@ -410,6 +460,12 @@ RSpec.describe API::BulkImports, feature_category: :importers do
|
|||
expect(json_response['id']).to eq(entity_2.id)
|
||||
end
|
||||
|
||||
it 'includes entity stats' do
|
||||
request
|
||||
|
||||
expect(json_response['stats']).to eq({ 'milestones' => { 'source' => 5, 'fetched' => 4, 'imported' => 3 } })
|
||||
end
|
||||
|
||||
it_behaves_like 'disabled feature'
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ RSpec.describe BulkImports::RelationExportService, feature_category: :importers
|
|||
expect(export.batched?).to eq(false)
|
||||
expect(export.batches_count).to eq(0)
|
||||
expect(export.batches.count).to eq(0)
|
||||
expect(export.total_objects_count).to eq(0)
|
||||
expect(export.total_objects_count).to eq(1)
|
||||
end
|
||||
|
||||
it 'removes temp export files' do
|
||||
|
|
|
|||
|
|
@ -105,6 +105,19 @@ RSpec.describe Notes::QuickActionsService, feature_category: :team_planning do
|
|||
end
|
||||
end
|
||||
|
||||
context 'with a timecategory' do
|
||||
let!(:timelog_category) { create(:timelog_category, name: 'bob', namespace: project.root_namespace) }
|
||||
let(:note_text) { "a note \n/spend 1h [timecategory:bob]" }
|
||||
|
||||
it 'sets the category of the new timelog' do
|
||||
new_content, update_params = service.execute(note)
|
||||
note.update!(note: new_content)
|
||||
service.apply_updates(update_params, note)
|
||||
|
||||
expect(Timelog.last.timelog_category_id).to eq(timelog_category.id)
|
||||
end
|
||||
end
|
||||
|
||||
context 'adds a system note' do
|
||||
context 'when not specifying a date' do
|
||||
let(:note_text) { "/spend 1h" }
|
||||
|
|
|
|||
|
|
@ -3,6 +3,15 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Projects::AutocompleteService, feature_category: :groups_and_projects do
|
||||
let_it_be(:group) { create(:group, :crm_enabled) }
|
||||
let_it_be(:project) { create(:project, :public, group: group) }
|
||||
let_it_be(:owner) { create(:user) }
|
||||
let_it_be(:issue) { create(:issue, project: project, title: 'Issue 1') }
|
||||
|
||||
before_all do
|
||||
project.add_owner(owner)
|
||||
end
|
||||
|
||||
describe '#issues' do
|
||||
describe 'confidential issues' do
|
||||
let(:author) { create(:user) }
|
||||
|
|
@ -10,8 +19,6 @@ RSpec.describe Projects::AutocompleteService, feature_category: :groups_and_proj
|
|||
let(:non_member) { create(:user) }
|
||||
let(:member) { create(:user) }
|
||||
let(:admin) { create(:admin) }
|
||||
let(:project) { create(:project, :public) }
|
||||
let!(:issue) { create(:issue, project: project, title: 'Issue 1') }
|
||||
let!(:security_issue_1) { create(:issue, :confidential, project: project, title: 'Security issue 1', author: author) }
|
||||
let!(:security_issue_2) { create(:issue, :confidential, title: 'Security issue 2', project: project, assignees: [assignee]) }
|
||||
|
||||
|
|
@ -107,8 +114,6 @@ RSpec.describe Projects::AutocompleteService, feature_category: :groups_and_proj
|
|||
|
||||
describe '#milestones' do
|
||||
let(:user) { create(:user) }
|
||||
let(:group) { create(:group) }
|
||||
let(:project) { create(:project, group: group) }
|
||||
let!(:group_milestone1) { create(:milestone, group: group, due_date: '2017-01-01', title: 'Second Title') }
|
||||
let!(:group_milestone2) { create(:milestone, group: group, due_date: '2017-01-01', title: 'First Title') }
|
||||
let!(:project_milestone) { create(:milestone, project: project, due_date: '2016-01-01') }
|
||||
|
|
@ -150,8 +155,6 @@ RSpec.describe Projects::AutocompleteService, feature_category: :groups_and_proj
|
|||
|
||||
describe '#contacts' do
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:group) { create(:group, :crm_enabled) }
|
||||
let_it_be(:project) { create(:project, group: group) }
|
||||
let_it_be(:contact_1) { create(:contact, group: group) }
|
||||
let_it_be(:contact_2) { create(:contact, group: group) }
|
||||
let_it_be(:contact_3) { create(:contact, :inactive, group: group) }
|
||||
|
|
@ -252,4 +255,30 @@ RSpec.describe Projects::AutocompleteService, feature_category: :groups_and_proj
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#commands' do
|
||||
subject(:commands) { described_class.new(project, owner).commands(issue) }
|
||||
|
||||
context 'spend' do
|
||||
it 'params include timecategory' do
|
||||
expect(commands).to include(a_hash_including(
|
||||
name: :spend,
|
||||
params: ['time(1h30m | -1h30m) <date(YYYY-MM-DD)> <[timecategory:category-name]>']
|
||||
))
|
||||
end
|
||||
|
||||
context 'when timelog_category_quick_action feature flag is disabled' do
|
||||
before do
|
||||
stub_feature_flags(timelog_categories: false)
|
||||
end
|
||||
|
||||
it 'params do not include timecategory' do
|
||||
expect(commands).to include(a_hash_including(
|
||||
name: :spend,
|
||||
params: ['time(1h30m | -1h30m) <date(YYYY-MM-DD)>']
|
||||
))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -384,6 +384,7 @@ RSpec.describe QuickActions::InterpretService, feature_category: :team_planning
|
|||
_, updates, _ = service.execute(content, issuable)
|
||||
|
||||
expect(updates).to eq(spend_time: {
|
||||
category: nil,
|
||||
duration: 3600,
|
||||
user_id: developer.id,
|
||||
spent_at: DateTime.current
|
||||
|
|
@ -398,6 +399,7 @@ RSpec.describe QuickActions::InterpretService, feature_category: :team_planning
|
|||
_, updates, _ = service.execute(content, issuable)
|
||||
|
||||
expect(updates).to eq(spend_time: {
|
||||
category: nil,
|
||||
duration: -7200,
|
||||
user_id: developer.id,
|
||||
spent_at: DateTime.current
|
||||
|
|
@ -417,6 +419,7 @@ RSpec.describe QuickActions::InterpretService, feature_category: :team_planning
|
|||
_, updates, _ = service.execute(content, issuable)
|
||||
|
||||
expect(updates).to eq(spend_time: {
|
||||
category: nil,
|
||||
duration: 1800,
|
||||
user_id: developer.id,
|
||||
spent_at: Date.parse(date)
|
||||
|
|
@ -440,6 +443,14 @@ RSpec.describe QuickActions::InterpretService, feature_category: :team_planning
|
|||
end
|
||||
end
|
||||
|
||||
shared_examples 'spend command with category' do
|
||||
it 'populates spend_time with expected attributes' do
|
||||
_, updates, _ = service.execute(content, issuable)
|
||||
|
||||
expect(updates).to match(spend_time: a_hash_including(category: 'pm'))
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'remove_estimate command' do
|
||||
it 'populates time_estimate: 0 if content contains /remove_estimate' do
|
||||
_, updates, _ = service.execute(content, issuable)
|
||||
|
|
@ -1544,6 +1555,11 @@ RSpec.describe QuickActions::InterpretService, feature_category: :team_planning
|
|||
let(:issuable) { issue }
|
||||
end
|
||||
|
||||
it_behaves_like 'spend command with category' do
|
||||
let(:content) { '/spent 30m [timecategory:pm]' }
|
||||
let(:issuable) { issue }
|
||||
end
|
||||
|
||||
it_behaves_like 'failed command' do
|
||||
let(:content) { '/spend' }
|
||||
let(:issuable) { issue }
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@ RSpec.shared_examples 'wiki pipeline imports a wiki for an entity' do
|
|||
allow_next_instance_of(BulkImports::Common::Extractors::GraphqlExtractor) do |extractor|
|
||||
allow(extractor).to receive(:extract).and_return(extracted_data)
|
||||
end
|
||||
|
||||
allow(subject).to receive(:set_source_objects_counter)
|
||||
end
|
||||
|
||||
context 'when wiki exists' do
|
||||
|
|
|
|||
Loading…
Reference in New Issue