Commit e9bd5871 authored by Peter Leitzen's avatar Peter Leitzen

Merge branch 'jh-235732_csv_import_gmau' into 'master'

Create CSV Issue Imports table

See merge request gitlab-org/gitlab!44742
parents 98c02d63 19f862ec
# frozen_string_literal: true
class CsvIssueImport < ApplicationRecord
belongs_to :project, optional: false
belongs_to :user, optional: false
end
...@@ -10,6 +10,7 @@ module Issues ...@@ -10,6 +10,7 @@ module Issues
end end
def execute def execute
record_import_attempt
process_csv process_csv
email_results_to_user email_results_to_user
...@@ -18,6 +19,10 @@ module Issues ...@@ -18,6 +19,10 @@ module Issues
private private
def record_import_attempt
CsvIssueImport.create!(user: @user, project: @project)
end
def process_csv def process_csv
csv_data = @csv_io.open(&:read).force_encoding(Encoding::UTF_8) csv_data = @csv_io.open(&:read).force_encoding(Encoding::UTF_8)
......
---
title: Add usage ping for unique users importing issues via CSV
merge_request: 44742
author:
type: changed
# frozen_string_literal: true
class CreateCsvIssueImports < ActiveRecord::Migration[6.0]
DOWNTIME = false
def up
create_table :csv_issue_imports do |t|
t.bigint :project_id, null: false, index: true
t.bigint :user_id, null: false, index: true
t.timestamps_with_timezone
end
end
def down
drop_table :csv_issue_imports
end
end
# frozen_string_literal: true
class AddProjectForeignKeyToCsvIssueImports < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_concurrent_foreign_key :csv_issue_imports, :projects, column: :project_id
end
def down
with_lock_retries do
remove_foreign_key :csv_issue_imports, column: :project_id
end
end
end
# frozen_string_literal: true
class AddUserForeignKeyToCsvIssueImports < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_concurrent_foreign_key :csv_issue_imports, :users, column: :user_id
end
def down
with_lock_retries do
remove_foreign_key :csv_issue_imports, column: :user_id
end
end
end
b7b15717435e06ea386c941c5a021b73f986ad3f41f2bd6f890738a79b710207
\ No newline at end of file
b8d5890fe5569127e794eea831fc18f8e5708b326e8037963fe2f6ae7f120e27
\ No newline at end of file
76e5a5bb535019da28041d1c7dddd8a18d3b04f688b38edc0c4214d3a4d6e357
\ No newline at end of file
...@@ -11270,6 +11270,23 @@ CREATE SEQUENCE conversational_development_index_metrics_id_seq ...@@ -11270,6 +11270,23 @@ CREATE SEQUENCE conversational_development_index_metrics_id_seq
ALTER SEQUENCE conversational_development_index_metrics_id_seq OWNED BY conversational_development_index_metrics.id; ALTER SEQUENCE conversational_development_index_metrics_id_seq OWNED BY conversational_development_index_metrics.id;
CREATE TABLE csv_issue_imports (
id bigint NOT NULL,
project_id bigint NOT NULL,
user_id bigint NOT NULL,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL
);
CREATE SEQUENCE csv_issue_imports_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE csv_issue_imports_id_seq OWNED BY csv_issue_imports.id;
CREATE TABLE custom_emoji ( CREATE TABLE custom_emoji (
id bigint NOT NULL, id bigint NOT NULL,
namespace_id bigint NOT NULL, namespace_id bigint NOT NULL,
...@@ -17523,6 +17540,8 @@ ALTER TABLE ONLY container_repositories ALTER COLUMN id SET DEFAULT nextval('con ...@@ -17523,6 +17540,8 @@ ALTER TABLE ONLY container_repositories ALTER COLUMN id SET DEFAULT nextval('con
ALTER TABLE ONLY conversational_development_index_metrics ALTER COLUMN id SET DEFAULT nextval('conversational_development_index_metrics_id_seq'::regclass); ALTER TABLE ONLY conversational_development_index_metrics ALTER COLUMN id SET DEFAULT nextval('conversational_development_index_metrics_id_seq'::regclass);
ALTER TABLE ONLY csv_issue_imports ALTER COLUMN id SET DEFAULT nextval('csv_issue_imports_id_seq'::regclass);
ALTER TABLE ONLY custom_emoji ALTER COLUMN id SET DEFAULT nextval('custom_emoji_id_seq'::regclass); ALTER TABLE ONLY custom_emoji ALTER COLUMN id SET DEFAULT nextval('custom_emoji_id_seq'::regclass);
ALTER TABLE ONLY dast_scanner_profiles ALTER COLUMN id SET DEFAULT nextval('dast_scanner_profiles_id_seq'::regclass); ALTER TABLE ONLY dast_scanner_profiles ALTER COLUMN id SET DEFAULT nextval('dast_scanner_profiles_id_seq'::regclass);
...@@ -18582,6 +18601,9 @@ ALTER TABLE ONLY container_repositories ...@@ -18582,6 +18601,9 @@ ALTER TABLE ONLY container_repositories
ALTER TABLE ONLY conversational_development_index_metrics ALTER TABLE ONLY conversational_development_index_metrics
ADD CONSTRAINT conversational_development_index_metrics_pkey PRIMARY KEY (id); ADD CONSTRAINT conversational_development_index_metrics_pkey PRIMARY KEY (id);
ALTER TABLE ONLY csv_issue_imports
ADD CONSTRAINT csv_issue_imports_pkey PRIMARY KEY (id);
ALTER TABLE ONLY custom_emoji ALTER TABLE ONLY custom_emoji
ADD CONSTRAINT custom_emoji_pkey PRIMARY KEY (id); ADD CONSTRAINT custom_emoji_pkey PRIMARY KEY (id);
...@@ -20222,6 +20244,10 @@ CREATE INDEX index_container_repository_on_name_trigram ON container_repositorie ...@@ -20222,6 +20244,10 @@ CREATE INDEX index_container_repository_on_name_trigram ON container_repositorie
CREATE INDEX index_created_at_on_codeowner_approval_merge_request_rules ON approval_merge_request_rules USING btree (created_at) WHERE ((rule_type = 2) AND (section <> 'codeowners'::text)); CREATE INDEX index_created_at_on_codeowner_approval_merge_request_rules ON approval_merge_request_rules USING btree (created_at) WHERE ((rule_type = 2) AND (section <> 'codeowners'::text));
CREATE INDEX index_csv_issue_imports_on_project_id ON csv_issue_imports USING btree (project_id);
CREATE INDEX index_csv_issue_imports_on_user_id ON csv_issue_imports USING btree (user_id);
CREATE UNIQUE INDEX index_custom_emoji_on_namespace_id_and_name ON custom_emoji USING btree (namespace_id, name); CREATE UNIQUE INDEX index_custom_emoji_on_namespace_id_and_name ON custom_emoji USING btree (namespace_id, name);
CREATE UNIQUE INDEX index_daily_build_group_report_results_unique_columns ON ci_daily_build_group_report_results USING btree (project_id, ref_path, date, group_name); CREATE UNIQUE INDEX index_daily_build_group_report_results_unique_columns ON ci_daily_build_group_report_results USING btree (project_id, ref_path, date, group_name);
...@@ -22423,6 +22449,9 @@ ALTER TABLE ONLY deploy_keys_projects ...@@ -22423,6 +22449,9 @@ ALTER TABLE ONLY deploy_keys_projects
ALTER TABLE ONLY issue_assignees ALTER TABLE ONLY issue_assignees
ADD CONSTRAINT fk_5e0c8d9154 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE; ADD CONSTRAINT fk_5e0c8d9154 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
ALTER TABLE ONLY csv_issue_imports
ADD CONSTRAINT fk_5e1572387c FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
ALTER TABLE ONLY project_access_tokens ALTER TABLE ONLY project_access_tokens
ADD CONSTRAINT fk_5f7e8450e1 FOREIGN KEY (personal_access_token_id) REFERENCES personal_access_tokens(id) ON DELETE CASCADE; ADD CONSTRAINT fk_5f7e8450e1 FOREIGN KEY (personal_access_token_id) REFERENCES personal_access_tokens(id) ON DELETE CASCADE;
...@@ -22795,6 +22824,9 @@ ALTER TABLE ONLY merge_requests ...@@ -22795,6 +22824,9 @@ ALTER TABLE ONLY merge_requests
ALTER TABLE ONLY issue_links ALTER TABLE ONLY issue_links
ADD CONSTRAINT fk_e71bb44f1f FOREIGN KEY (target_id) REFERENCES issues(id) ON DELETE CASCADE; ADD CONSTRAINT fk_e71bb44f1f FOREIGN KEY (target_id) REFERENCES issues(id) ON DELETE CASCADE;
ALTER TABLE ONLY csv_issue_imports
ADD CONSTRAINT fk_e71c0ae362 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
ALTER TABLE ONLY namespaces ALTER TABLE ONLY namespaces
ADD CONSTRAINT fk_e7a0b20a6b FOREIGN KEY (custom_project_templates_group_id) REFERENCES namespaces(id) ON DELETE SET NULL; ADD CONSTRAINT fk_e7a0b20a6b FOREIGN KEY (custom_project_templates_group_id) REFERENCES namespaces(id) ON DELETE SET NULL;
......
...@@ -577,7 +577,8 @@ module Gitlab ...@@ -577,7 +577,8 @@ module Gitlab
issues_imported: { issues_imported: {
jira: distinct_count(::JiraImportState.where(time_period), :user_id), jira: distinct_count(::JiraImportState.where(time_period), :user_id),
fogbugz: projects_imported_count('fogbugz', time_period), fogbugz: projects_imported_count('fogbugz', time_period),
phabricator: projects_imported_count('phabricator', time_period) phabricator: projects_imported_count('phabricator', time_period),
csv: distinct_count(CsvIssueImport.where(time_period), :user_id)
}, },
groups_imported: distinct_count(::GroupImportState.where(time_period), :user_id) groups_imported: distinct_count(::GroupImportState.where(time_period), :user_id)
} }
......
# frozen_string_literal: true
FactoryBot.define do
factory :csv_issue_import do
project
user
end
end
...@@ -207,6 +207,8 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do ...@@ -207,6 +207,8 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
jira_project = create(:project, creator_id: user.id) jira_project = create(:project, creator_id: user.id)
create(:jira_import_state, :finished, project: jira_project) create(:jira_import_state, :finished, project: jira_project)
create(:csv_issue_import, user: user)
end end
expect(described_class.usage_activity_by_stage_manage({})).to include( expect(described_class.usage_activity_by_stage_manage({})).to include(
...@@ -224,7 +226,8 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do ...@@ -224,7 +226,8 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
issues_imported: { issues_imported: {
jira: 2, jira: 2,
fogbugz: 2, fogbugz: 2,
phabricator: 2 phabricator: 2,
csv: 2
} }
} }
) )
...@@ -243,7 +246,8 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do ...@@ -243,7 +246,8 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
issues_imported: { issues_imported: {
jira: 1, jira: 1,
fogbugz: 1, fogbugz: 1,
phabricator: 1 phabricator: 1,
csv: 1
} }
} }
) )
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe CsvIssueImport, type: :model do
describe 'associations' do
it { is_expected.to belong_to(:project).required }
it { is_expected.to belong_to(:user).required }
end
end
...@@ -13,6 +13,14 @@ RSpec.describe Issues::ImportCsvService do ...@@ -13,6 +13,14 @@ RSpec.describe Issues::ImportCsvService do
described_class.new(user, project, uploader).execute described_class.new(user, project, uploader).execute
end end
shared_examples_for 'an issue importer' do
it 'records the import attempt' do
expect { subject }
.to change { CsvIssueImport.where(project: project, user: user).count }
.by 1
end
end
describe '#execute' do describe '#execute' do
context 'invalid file' do context 'invalid file' do
let(:file) { fixture_file_upload('spec/fixtures/banana_sample.gif') } let(:file) { fixture_file_upload('spec/fixtures/banana_sample.gif') }
...@@ -23,6 +31,8 @@ RSpec.describe Issues::ImportCsvService do ...@@ -23,6 +31,8 @@ RSpec.describe Issues::ImportCsvService do
expect(subject[:success]).to eq(0) expect(subject[:success]).to eq(0)
expect(subject[:parse_error]).to eq(true) expect(subject[:parse_error]).to eq(true)
end end
it_behaves_like 'an issue importer'
end end
context 'with a file generated by Gitlab CSV export' do context 'with a file generated by Gitlab CSV export' do
...@@ -44,6 +54,8 @@ RSpec.describe Issues::ImportCsvService do ...@@ -44,6 +54,8 @@ RSpec.describe Issues::ImportCsvService do
description: 'Test Description' description: 'Test Description'
) )
end end
it_behaves_like 'an issue importer'
end end
context 'comma delimited file' do context 'comma delimited file' do
...@@ -65,6 +77,8 @@ RSpec.describe Issues::ImportCsvService do ...@@ -65,6 +77,8 @@ RSpec.describe Issues::ImportCsvService do
description: 'Description' description: 'Description'
) )
end end
it_behaves_like 'an issue importer'
end end
context 'tab delimited file with error row' do context 'tab delimited file with error row' do
...@@ -86,6 +100,8 @@ RSpec.describe Issues::ImportCsvService do ...@@ -86,6 +100,8 @@ RSpec.describe Issues::ImportCsvService do
description: 'World' description: 'World'
) )
end end
it_behaves_like 'an issue importer'
end end
context 'semicolon delimited file with CRLF' do context 'semicolon delimited file with CRLF' do
...@@ -107,6 +123,8 @@ RSpec.describe Issues::ImportCsvService do ...@@ -107,6 +123,8 @@ RSpec.describe Issues::ImportCsvService do
description: 'World' description: 'World'
) )
end end
it_behaves_like 'an issue importer'
end end
end end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment