data_porter 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.claude/commands/blog-status.md +10 -0
- data/.claude/commands/blog.md +109 -0
- data/.claude/commands/task-done.md +27 -0
- data/.claude/commands/tm/add-dependency.md +58 -0
- data/.claude/commands/tm/add-subtask.md +79 -0
- data/.claude/commands/tm/add-task.md +81 -0
- data/.claude/commands/tm/analyze-complexity.md +124 -0
- data/.claude/commands/tm/analyze-project.md +100 -0
- data/.claude/commands/tm/auto-implement-tasks.md +100 -0
- data/.claude/commands/tm/command-pipeline.md +80 -0
- data/.claude/commands/tm/complexity-report.md +120 -0
- data/.claude/commands/tm/convert-task-to-subtask.md +74 -0
- data/.claude/commands/tm/expand-all-tasks.md +52 -0
- data/.claude/commands/tm/expand-task.md +52 -0
- data/.claude/commands/tm/fix-dependencies.md +82 -0
- data/.claude/commands/tm/help.md +101 -0
- data/.claude/commands/tm/init-project-quick.md +49 -0
- data/.claude/commands/tm/init-project.md +53 -0
- data/.claude/commands/tm/install-taskmaster.md +118 -0
- data/.claude/commands/tm/learn.md +106 -0
- data/.claude/commands/tm/list-tasks-by-status.md +42 -0
- data/.claude/commands/tm/list-tasks-with-subtasks.md +30 -0
- data/.claude/commands/tm/list-tasks.md +46 -0
- data/.claude/commands/tm/next-task.md +69 -0
- data/.claude/commands/tm/parse-prd-with-research.md +51 -0
- data/.claude/commands/tm/parse-prd.md +52 -0
- data/.claude/commands/tm/project-status.md +67 -0
- data/.claude/commands/tm/quick-install-taskmaster.md +23 -0
- data/.claude/commands/tm/remove-all-subtasks.md +94 -0
- data/.claude/commands/tm/remove-dependency.md +65 -0
- data/.claude/commands/tm/remove-subtask.md +87 -0
- data/.claude/commands/tm/remove-subtasks.md +89 -0
- data/.claude/commands/tm/remove-task.md +110 -0
- data/.claude/commands/tm/setup-models.md +52 -0
- data/.claude/commands/tm/show-task.md +85 -0
- data/.claude/commands/tm/smart-workflow.md +58 -0
- data/.claude/commands/tm/sync-readme.md +120 -0
- data/.claude/commands/tm/tm-main.md +147 -0
- data/.claude/commands/tm/to-cancelled.md +58 -0
- data/.claude/commands/tm/to-deferred.md +50 -0
- data/.claude/commands/tm/to-done.md +47 -0
- data/.claude/commands/tm/to-in-progress.md +39 -0
- data/.claude/commands/tm/to-pending.md +35 -0
- data/.claude/commands/tm/to-review.md +43 -0
- data/.claude/commands/tm/update-single-task.md +122 -0
- data/.claude/commands/tm/update-task.md +75 -0
- data/.claude/commands/tm/update-tasks-from-id.md +111 -0
- data/.claude/commands/tm/validate-dependencies.md +72 -0
- data/.claude/commands/tm/view-models.md +52 -0
- data/.env.example +12 -0
- data/.mcp.json +24 -0
- data/.taskmaster/CLAUDE.md +435 -0
- data/.taskmaster/config.json +44 -0
- data/.taskmaster/docs/prd.txt +2044 -0
- data/.taskmaster/state.json +6 -0
- data/.taskmaster/tasks/task_001.md +19 -0
- data/.taskmaster/tasks/task_002.md +19 -0
- data/.taskmaster/tasks/task_003.md +19 -0
- data/.taskmaster/tasks/task_004.md +19 -0
- data/.taskmaster/tasks/task_005.md +19 -0
- data/.taskmaster/tasks/task_006.md +19 -0
- data/.taskmaster/tasks/task_007.md +19 -0
- data/.taskmaster/tasks/task_008.md +19 -0
- data/.taskmaster/tasks/task_009.md +19 -0
- data/.taskmaster/tasks/task_010.md +19 -0
- data/.taskmaster/tasks/task_011.md +19 -0
- data/.taskmaster/tasks/task_012.md +19 -0
- data/.taskmaster/tasks/task_013.md +19 -0
- data/.taskmaster/tasks/task_014.md +19 -0
- data/.taskmaster/tasks/task_015.md +19 -0
- data/.taskmaster/tasks/task_016.md +19 -0
- data/.taskmaster/tasks/task_017.md +19 -0
- data/.taskmaster/tasks/task_018.md +19 -0
- data/.taskmaster/tasks/task_019.md +19 -0
- data/.taskmaster/tasks/task_020.md +19 -0
- data/.taskmaster/tasks/tasks.json +299 -0
- data/.taskmaster/templates/example_prd.txt +47 -0
- data/.taskmaster/templates/example_prd_rpg.txt +511 -0
- data/CHANGELOG.md +29 -0
- data/CLAUDE.md +65 -0
- data/CODE_OF_CONDUCT.md +10 -0
- data/CONTRIBUTING.md +49 -0
- data/LICENSE +21 -0
- data/README.md +463 -0
- data/Rakefile +12 -0
- data/app/assets/stylesheets/data_porter/application.css +646 -0
- data/app/channels/data_porter/import_channel.rb +10 -0
- data/app/controllers/data_porter/imports_controller.rb +68 -0
- data/app/javascript/data_porter/progress_controller.js +33 -0
- data/app/jobs/data_porter/dry_run_job.rb +12 -0
- data/app/jobs/data_porter/import_job.rb +12 -0
- data/app/jobs/data_porter/parse_job.rb +12 -0
- data/app/models/data_porter/data_import.rb +49 -0
- data/app/views/data_porter/imports/index.html.erb +142 -0
- data/app/views/data_porter/imports/new.html.erb +88 -0
- data/app/views/data_porter/imports/show.html.erb +49 -0
- data/config/database.yml +3 -0
- data/config/routes.rb +12 -0
- data/docs/SPEC.md +2012 -0
- data/docs/UI.md +32 -0
- data/docs/blog/001-why-build-a-data-import-engine.md +166 -0
- data/docs/blog/002-scaffolding-a-rails-engine.md +188 -0
- data/docs/blog/003-configuration-dsl.md +222 -0
- data/docs/blog/004-store-model-jsonb.md +237 -0
- data/docs/blog/005-target-dsl.md +284 -0
- data/docs/blog/006-parsing-csv-sources.md +300 -0
- data/docs/blog/007-orchestrator.md +247 -0
- data/docs/blog/008-actioncable-stimulus.md +376 -0
- data/docs/blog/009-phlex-ui-components.md +446 -0
- data/docs/blog/010-controllers-routing.md +374 -0
- data/docs/blog/011-generators.md +364 -0
- data/docs/blog/012-json-api-sources.md +323 -0
- data/docs/blog/013-testing-rails-engine.md +618 -0
- data/docs/blog/014-dry-run.md +307 -0
- data/docs/blog/015-publishing-retro.md +264 -0
- data/docs/blog/016-erb-view-templates.md +431 -0
- data/docs/blog/017-showcase-final-retro.md +220 -0
- data/docs/blog/BACKLOG.md +8 -0
- data/docs/blog/SERIES.md +154 -0
- data/docs/screenshots/index-with-previewing.jpg +0 -0
- data/docs/screenshots/index.jpg +0 -0
- data/docs/screenshots/modal-new-import.jpg +0 -0
- data/docs/screenshots/preview.jpg +0 -0
- data/lib/data_porter/broadcaster.rb +29 -0
- data/lib/data_porter/components/base.rb +10 -0
- data/lib/data_porter/components/failure_alert.rb +20 -0
- data/lib/data_porter/components/preview_table.rb +54 -0
- data/lib/data_porter/components/progress_bar.rb +33 -0
- data/lib/data_porter/components/results_summary.rb +19 -0
- data/lib/data_porter/components/status_badge.rb +16 -0
- data/lib/data_porter/components/summary_cards.rb +30 -0
- data/lib/data_porter/components.rb +14 -0
- data/lib/data_porter/configuration.rb +25 -0
- data/lib/data_porter/dsl/api_config.rb +25 -0
- data/lib/data_porter/dsl/column.rb +17 -0
- data/lib/data_porter/engine.rb +15 -0
- data/lib/data_porter/orchestrator.rb +141 -0
- data/lib/data_porter/record_validator.rb +32 -0
- data/lib/data_porter/registry.rb +33 -0
- data/lib/data_porter/sources/api.rb +49 -0
- data/lib/data_porter/sources/base.rb +35 -0
- data/lib/data_porter/sources/csv.rb +43 -0
- data/lib/data_porter/sources/json.rb +45 -0
- data/lib/data_porter/sources.rb +20 -0
- data/lib/data_porter/store_models/error.rb +13 -0
- data/lib/data_porter/store_models/import_record.rb +52 -0
- data/lib/data_porter/store_models/report.rb +21 -0
- data/lib/data_porter/target.rb +89 -0
- data/lib/data_porter/type_validator.rb +46 -0
- data/lib/data_porter/version.rb +5 -0
- data/lib/data_porter.rb +32 -0
- data/lib/generators/data_porter/install/install_generator.rb +33 -0
- data/lib/generators/data_porter/install/templates/create_data_porter_imports.rb.erb +21 -0
- data/lib/generators/data_porter/install/templates/initializer.rb +30 -0
- data/lib/generators/data_porter/target/target_generator.rb +44 -0
- data/lib/generators/data_porter/target/templates/target.rb.tt +20 -0
- data/sig/data_porter.rbs +4 -0
- metadata +274 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Task ID: 1
|
|
2
|
+
|
|
3
|
+
**Title:** Setup gem structure and Rails Engine
|
|
4
|
+
|
|
5
|
+
**Status:** pending
|
|
6
|
+
|
|
7
|
+
**Dependencies:** None
|
|
8
|
+
|
|
9
|
+
**Priority:** high
|
|
10
|
+
|
|
11
|
+
**Description:** Set up the core gem directory structure as defined in the spec (app/, lib/, config/, spec/) and configure the Rails Engine with isolate_namespace DataPorter. Include engine.rb with asset precompilation initializer, ActiveStorage initializer, and auto-discovery of target files from app/importers/.
|
|
12
|
+
|
|
13
|
+
**Details:**
|
|
14
|
+
|
|
15
|
+
Create the full directory tree: app/{channels,controllers,jobs,models,views}/data_porter/, lib/data_porter/{sources,store_models,dsl}/, config/, spec/. The Engine class should inherit from ::Rails::Engine, call isolate_namespace DataPorter, and set up initializers for assets, active_storage, and target auto-discovery via config.to_prepare. Update data_porter.gemspec with dependencies: rails (>= 7.0), store_model, phlex, turbo-rails, stimulus-rails. Update lib/data_porter.rb to require all sub-modules.
|
|
16
|
+
|
|
17
|
+
**Test Strategy:**
|
|
18
|
+
|
|
19
|
+
Verify the engine loads without errors. Test that isolate_namespace is correctly applied. Test that the initializer discovers target files.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Task ID: 2
|
|
2
|
+
|
|
3
|
+
**Title:** Implement Configuration module
|
|
4
|
+
|
|
5
|
+
**Status:** pending
|
|
6
|
+
|
|
7
|
+
**Dependencies:** 1
|
|
8
|
+
|
|
9
|
+
**Priority:** high
|
|
10
|
+
|
|
11
|
+
**Description:** Create DataPorter::Configuration class with all config options and DataPorter.configure DSL. Options: parent_controller, queue_name, storage_service, cable_channel_prefix, context_builder, preview_limit, enabled_sources, scope.
|
|
12
|
+
|
|
13
|
+
**Details:**
|
|
14
|
+
|
|
15
|
+
Create lib/data_porter/configuration.rb with attr_accessors for: parent_controller (default: 'ApplicationController'), queue_name (default: :imports), storage_service (default: :local), cable_channel_prefix (default: 'data_porter'), context_builder (default: nil, accepts a lambda), preview_limit (default: 500), enabled_sources (default: [:csv, :json, :api]), scope (default: nil). Add DataPorter.configure class method yielding the config singleton, and DataPorter.configuration accessor.
|
|
16
|
+
|
|
17
|
+
**Test Strategy:**
|
|
18
|
+
|
|
19
|
+
Test default values. Test that configure block sets values correctly. Test that configuration is accessible via DataPorter.configuration.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Task ID: 3
|
|
2
|
+
|
|
3
|
+
**Title:** Create StoreModels (ImportRecord, Error, Report)
|
|
4
|
+
|
|
5
|
+
**Status:** pending
|
|
6
|
+
|
|
7
|
+
**Dependencies:** 1
|
|
8
|
+
|
|
9
|
+
**Priority:** high
|
|
10
|
+
|
|
11
|
+
**Description:** Implement the three StoreModel classes for JSONB storage: ImportRecord (line_number, status, data, errors_list, warnings, target_id), Error (message), Report (records_count, complete_count, partial_count, missing_count, duplicate_count, imported_count, errored_count, error_reports).
|
|
12
|
+
|
|
13
|
+
**Details:**
|
|
14
|
+
|
|
15
|
+
Create lib/data_porter/store_models/error.rb with StoreModel::Model including attribute :message. Create lib/data_porter/store_models/import_record.rb with attributes: line_number (:integer), status (:string, default: 'pending'), data (:json, default: {}), errors_list (Error.to_array_type), warnings (Error.to_array_type), target_id (:integer), dry_run_passed (:boolean, default: false). Include STATUSES constant, helper methods (complete?, importable?, add_error, add_warning, attributes), and run_validations! method with validate_required_columns!, validate_types!, validate_inclusions!, check_duplicates!, determine_status!. Create lib/data_porter/store_models/report.rb with all count attributes.
|
|
16
|
+
|
|
17
|
+
**Test Strategy:**
|
|
18
|
+
|
|
19
|
+
Test each StoreModel initializes with defaults. Test ImportRecord validation methods. Test status determination logic. Test add_error and add_warning methods.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Task ID: 4
|
|
2
|
+
|
|
3
|
+
**Title:** Implement TypeValidator
|
|
4
|
+
|
|
5
|
+
**Status:** pending
|
|
6
|
+
|
|
7
|
+
**Dependencies:** 1
|
|
8
|
+
|
|
9
|
+
**Priority:** high
|
|
10
|
+
|
|
11
|
+
**Description:** Create DataPorter::TypeValidator module with .valid?(value, type, options) class method supporting types: :string, :integer, :decimal, :date, :datetime, :email, :phone, :url, :boolean.
|
|
12
|
+
|
|
13
|
+
**Details:**
|
|
14
|
+
|
|
15
|
+
Create lib/data_porter/type_validator.rb. Implement validation for each type: string (always valid if present), integer (Integer(value) rescue false), decimal (Float(value) rescue false), date (Date.parse or strptime with format option), datetime (DateTime.parse), email (regex validation), phone (regex for common formats), url (URI.parse with scheme check), boolean (true/false/'true'/'false'/0/1).
|
|
16
|
+
|
|
17
|
+
**Test Strategy:**
|
|
18
|
+
|
|
19
|
+
Test each type with valid and invalid values. Test date with custom format option. Test edge cases (nil, empty string, weird types).
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Task ID: 5
|
|
2
|
+
|
|
3
|
+
**Title:** Build Target DSL base class
|
|
4
|
+
|
|
5
|
+
**Status:** pending
|
|
6
|
+
|
|
7
|
+
**Dependencies:** 3, 4
|
|
8
|
+
|
|
9
|
+
**Priority:** high
|
|
10
|
+
|
|
11
|
+
**Description:** Implement DataPorter::Target base class with all DSL class methods: label, model, icon, sources, columns, csv_mapping, deduplicate_by, params, api_config. Include overridable instance hooks: transform, validate, persist, after_import, on_error.
|
|
12
|
+
|
|
13
|
+
**Details:**
|
|
14
|
+
|
|
15
|
+
Create lib/data_porter/target.rb. Implement class-level DSL using class_attribute or class instance variables with accessor methods: _label, _model, _icon, _sources, _columns, _csv_mappings, _dedup_keys, _params, _api_config, _dry_run_enabled, _rollback_enabled, _rollback_window, _rollback_strategy. Create DSL sub-modules in lib/data_porter/dsl/: columns_dsl.rb (Column struct with name, type, required, options, label), csv_mapping_dsl.rb, api_config_dsl.rb, params_dsl.rb. Default hook implementations: transform returns record unchanged, validate is no-op, persist raises NotImplementedError, after_import is no-op, on_error is no-op.
|
|
16
|
+
|
|
17
|
+
**Test Strategy:**
|
|
18
|
+
|
|
19
|
+
Test DSL methods set class attributes correctly. Test a sample target definition. Test default hooks. Test column definition with all options. Test inheritance of DSL attributes.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Task ID: 6
|
|
2
|
+
|
|
3
|
+
**Title:** Implement Registry and auto-discovery
|
|
4
|
+
|
|
5
|
+
**Status:** pending
|
|
6
|
+
|
|
7
|
+
**Dependencies:** 5
|
|
8
|
+
|
|
9
|
+
**Priority:** high
|
|
10
|
+
|
|
11
|
+
**Description:** Create DataPorter::Registry singleton with class methods: register, find, available, refresh!. Targets auto-register on inheritance using Target.descendants.
|
|
12
|
+
|
|
13
|
+
**Details:**
|
|
14
|
+
|
|
15
|
+
Create lib/data_porter/registry.rb. The Registry stores targets in a Hash keyed by parameterized label. register(key, klass) adds to the hash. find(key) looks up or raises DataPorter::TargetNotFound. available returns array of {key:, label:, icon:} hashes. refresh! clears and re-registers all Target descendants that have a _label set. Define DataPorter::TargetNotFound error class.
|
|
16
|
+
|
|
17
|
+
**Test Strategy:**
|
|
18
|
+
|
|
19
|
+
Test register and find. Test TargetNotFound error. Test available returns correct format. Test refresh! discovers target subclasses.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Task ID: 7
|
|
2
|
+
|
|
3
|
+
**Title:** Create DataImport model and migration
|
|
4
|
+
|
|
5
|
+
**Status:** pending
|
|
6
|
+
|
|
7
|
+
**Dependencies:** 3, 6
|
|
8
|
+
|
|
9
|
+
**Priority:** high
|
|
10
|
+
|
|
11
|
+
**Description:** Implement DataPorter::DataImport ActiveRecord model with enum status, JSONB attributes (records, report, config), ActiveStorage file attachment, polymorphic user association. Create the migration template.
|
|
12
|
+
|
|
13
|
+
**Details:**
|
|
14
|
+
|
|
15
|
+
Create app/models/data_porter/data_import.rb with: table_name 'data_porter_imports', belongs_to :user (polymorphic), has_one_attached :file, enum :status (pending:0, parsing:1, previewing:2, importing:3, completed:4, failed:5, dry_running:6, rolling_back:7, rolled_back:8), StoreModel attributes for records and report, serialized config. Add validations for target_key and source_type. Implement helper methods: target_class, source_class, previewable?, importable_records, records_summary. Create migration template in lib/generators/data_porter/templates/ with proper schema (target_key, source_type, status, records JSONB, report JSONB, config JSONB, user polymorphic refs, timestamps, indexes).
|
|
16
|
+
|
|
17
|
+
**Test Strategy:**
|
|
18
|
+
|
|
19
|
+
Test enum values. Test validations. Test helper methods. Test StoreModel attribute types. Test associations.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Task ID: 8
|
|
2
|
+
|
|
3
|
+
**Title:** Implement Source base class and CSV source
|
|
4
|
+
|
|
5
|
+
**Status:** pending
|
|
6
|
+
|
|
7
|
+
**Dependencies:** 5, 7
|
|
8
|
+
|
|
9
|
+
**Priority:** high
|
|
10
|
+
|
|
11
|
+
**Description:** Create DataPorter::Sources::Base abstract class and DataPorter::Sources::Csv implementation. Include DataPorter::Sources.resolve class method for source type lookup.
|
|
12
|
+
|
|
13
|
+
**Details:**
|
|
14
|
+
|
|
15
|
+
Create lib/data_porter/sources/base.rb with initialize(data_import), abstract fetch method, and private apply_csv_mapping helper (uses target's _csv_mappings or falls back to parameterize auto-mapping). Create lib/data_porter/sources/csv.rb extending Base: fetch reads the attached file via ActiveStorage, parses with Ruby CSV (headers: true + target's csv_options), applies csv_mapping to each row. Create lib/data_porter/sources.rb with resolve(type) method that maps 'csv' -> Csv, 'json' -> Json, 'api' -> Api.
|
|
16
|
+
|
|
17
|
+
**Test Strategy:**
|
|
18
|
+
|
|
19
|
+
Test CSV parsing with headers. Test csv_mapping application (explicit and auto). Test resolve method returns correct class. Test with various CSV formats (different separators, encodings).
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Task ID: 9
|
|
2
|
+
|
|
3
|
+
**Title:** Build Orchestrator (parse and import flows)
|
|
4
|
+
|
|
5
|
+
**Status:** pending
|
|
6
|
+
|
|
7
|
+
**Dependencies:** 7, 8
|
|
8
|
+
|
|
9
|
+
**Priority:** high
|
|
10
|
+
|
|
11
|
+
**Description:** Implement DataPorter::Orchestrator that coordinates source + target for parse! and import! operations. Handles the full workflow: fetch data, transform, validate, build records, persist.
|
|
12
|
+
|
|
13
|
+
**Details:**
|
|
14
|
+
|
|
15
|
+
Create lib/data_porter/orchestrator.rb. initialize(data_import) sets up @source and @target instances. parse!: transitions to parsing, fetches raw rows from source, maps to ImportRecord StoreModels (applying transform and validate hooks, running run_validations!), broadcasts progress, saves records and transitions to previewing, builds report. import!: transitions to importing, iterates importable_records, calls target.persist for each with context, handles per-record errors via on_error hook, updates counts, transitions to completed, calls after_import. Private methods: broadcast_progress, broadcast_success, handle_failure (transitions to failed with error report), build_context (calls context_builder), build_report.
|
|
16
|
+
|
|
17
|
+
**Test Strategy:**
|
|
18
|
+
|
|
19
|
+
Test parse! creates ImportRecords from source data. Test import! calls persist for each importable record. Test error handling during parse and import. Test status transitions. Test progress broadcasting.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Task ID: 10
|
|
2
|
+
|
|
3
|
+
**Title:** Create ActiveJob classes (ParseJob, ImportJob)
|
|
4
|
+
|
|
5
|
+
**Status:** pending
|
|
6
|
+
|
|
7
|
+
**Dependencies:** 9
|
|
8
|
+
|
|
9
|
+
**Priority:** high
|
|
10
|
+
|
|
11
|
+
**Description:** Implement DataPorter::ParseJob and DataPorter::ImportJob that delegate to the Orchestrator. Both use configurable queue_name.
|
|
12
|
+
|
|
13
|
+
**Details:**
|
|
14
|
+
|
|
15
|
+
Create app/jobs/data_porter/parse_job.rb: inherits ActiveJob::Base, queue_as from DataPorter.configuration.queue_name, perform(import_id) finds DataImport and calls Orchestrator.new(data_import).parse!. Create app/jobs/data_porter/import_job.rb: same pattern but calls orchestrator.import!.
|
|
16
|
+
|
|
17
|
+
**Test Strategy:**
|
|
18
|
+
|
|
19
|
+
Test jobs enqueue correctly. Test they find the DataImport and call the right orchestrator method. Test queue configuration.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Task ID: 11
|
|
2
|
+
|
|
3
|
+
**Title:** Implement ActionCable Broadcaster and ImportChannel
|
|
4
|
+
|
|
5
|
+
**Status:** pending
|
|
6
|
+
|
|
7
|
+
**Dependencies:** 1
|
|
8
|
+
|
|
9
|
+
**Priority:** medium
|
|
10
|
+
|
|
11
|
+
**Description:** Create DataPorter::Broadcaster for real-time progress updates and DataPorter::ImportChannel for client subscriptions.
|
|
12
|
+
|
|
13
|
+
**Details:**
|
|
14
|
+
|
|
15
|
+
Create lib/data_porter/broadcaster.rb: initialize(import_id) builds channel name from cable_channel_prefix config. progress(current, total) broadcasts percentage. success() broadcasts success status. failure(message) broadcasts failure with error. Create app/channels/data_porter/import_channel.rb: subscribes to the import-specific channel stream.
|
|
16
|
+
|
|
17
|
+
**Test Strategy:**
|
|
18
|
+
|
|
19
|
+
Test broadcaster formats messages correctly. Test channel subscribes to correct stream. Test progress percentage calculation.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Task ID: 12
|
|
2
|
+
|
|
3
|
+
**Title:** Build ImportsController with all actions
|
|
4
|
+
|
|
5
|
+
**Status:** pending
|
|
6
|
+
|
|
7
|
+
**Dependencies:** 7, 10
|
|
8
|
+
|
|
9
|
+
**Priority:** high
|
|
10
|
+
|
|
11
|
+
**Description:** Implement DataPorter::ImportsController inheriting from configured parent_controller. Actions: index, new, create, show, parse, confirm, cancel. Wire up routes.
|
|
12
|
+
|
|
13
|
+
**Details:**
|
|
14
|
+
|
|
15
|
+
Create app/controllers/data_porter/imports_controller.rb inheriting from DataPorter.configuration.parent_controller.constantize. before_action :set_import for show/parse/confirm/cancel. index: list imports ordered by created_at desc. new: build new DataImport, load available targets. create: build DataImport from params, assign current_user, enqueue ParseJob on save. show: load target, records, grouped records. parse: reset to pending and re-enqueue ParseJob. confirm: enqueue ImportJob. cancel: set status to failed. Create config/routes.rb with DataPorter::Engine.routes.draw: resources :imports with member routes for parse, confirm, cancel.
|
|
16
|
+
|
|
17
|
+
**Test Strategy:**
|
|
18
|
+
|
|
19
|
+
Test each controller action. Test strong params. Test job enqueuing on create and confirm. Test status transitions on parse and cancel.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Task ID: 13
|
|
2
|
+
|
|
3
|
+
**Title:** Create Phlex view components for the UI
|
|
4
|
+
|
|
5
|
+
**Status:** pending
|
|
6
|
+
|
|
7
|
+
**Dependencies:** 12
|
|
8
|
+
|
|
9
|
+
**Priority:** high
|
|
10
|
+
|
|
11
|
+
**Description:** Build all UI components using Phlex with Tailwind CSS (dp- prefix, .data-porter scope). Components: Layout, ImportsList, NewImportForm, ImportShow (with Preview table, Progress bar, Summary cards, Actions, Results, Failure alert).
|
|
12
|
+
|
|
13
|
+
**Details:**
|
|
14
|
+
|
|
15
|
+
Create Phlex components in lib/data_porter/components/ or app/views/data_porter/components/. All Tailwind classes use dp- prefix. Root wrapper uses .data-porter class for scoping. Components: ImportsList (table with status badges, links), NewImportForm (target select, source type, file upload, config fields), ImportShow (delegates to sub-components based on status), PreviewTable (dynamic columns from target, status icons, error display, row coloring by status), ProgressBar (Stimulus-connected, percentage display), SummaryCards (complete/partial/missing/duplicate counts), ActionButtons (cancel, confirm with count), ResultsSummary (created/errored counts), FailureAlert (error messages, retry button). Use CSS custom properties for theming: --dp-color-complete, --dp-color-partial, --dp-color-missing, --dp-color-primary.
|
|
16
|
+
|
|
17
|
+
**Test Strategy:**
|
|
18
|
+
|
|
19
|
+
Test each component renders correct HTML structure. Test conditional rendering based on import status. Test dynamic columns from target. Test CSS class prefixing.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Task ID: 14
|
|
2
|
+
|
|
3
|
+
**Title:** Implement Stimulus progress controller
|
|
4
|
+
|
|
5
|
+
**Status:** pending
|
|
6
|
+
|
|
7
|
+
**Dependencies:** 11, 13
|
|
8
|
+
|
|
9
|
+
**Priority:** medium
|
|
10
|
+
|
|
11
|
+
**Description:** Create the Stimulus controller for real-time progress updates via ActionCable. Connects to ImportChannel, updates progress bar and auto-reloads on completion.
|
|
12
|
+
|
|
13
|
+
**Details:**
|
|
14
|
+
|
|
15
|
+
Create app/javascript/data_porter/progress_controller.js (or app/assets/javascript/). Stimulus controller with targets: bar, text. Values: id (Number). On connect: subscribe to ImportChannel with the import id. On received: if status is 'processing', update bar width and text with percentage. If status is 'success' or 'failure', reload page. On disconnect: unsubscribe.
|
|
16
|
+
|
|
17
|
+
**Test Strategy:**
|
|
18
|
+
|
|
19
|
+
Test controller connects to correct channel. Test progress bar updates. Test page reload on completion.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Task ID: 15
|
|
2
|
+
|
|
3
|
+
**Title:** Create install generator
|
|
4
|
+
|
|
5
|
+
**Status:** pending
|
|
6
|
+
|
|
7
|
+
**Dependencies:** 7, 2
|
|
8
|
+
|
|
9
|
+
**Priority:** medium
|
|
10
|
+
|
|
11
|
+
**Description:** Implement rails generate data_porter:install generator that creates migration, initializer, importers directory, and mounts the engine in routes.
|
|
12
|
+
|
|
13
|
+
**Details:**
|
|
14
|
+
|
|
15
|
+
Create lib/generators/data_porter/install_generator.rb. It should: copy migration template (create_data_porter_imports) with proper timestamp, create config/initializers/data_porter.rb with commented configuration options, create empty app/importers/ directory, inject mount DataPorter::Engine at: '/imports' into config/routes.rb. Use Rails::Generators::Base with source_root pointing to templates/.
|
|
16
|
+
|
|
17
|
+
**Test Strategy:**
|
|
18
|
+
|
|
19
|
+
Test generator creates all expected files. Test migration has correct schema. Test initializer has all config options. Test route injection.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Task ID: 16
|
|
2
|
+
|
|
3
|
+
**Title:** Create target generator
|
|
4
|
+
|
|
5
|
+
**Status:** pending
|
|
6
|
+
|
|
7
|
+
**Dependencies:** 5
|
|
8
|
+
|
|
9
|
+
**Priority:** medium
|
|
10
|
+
|
|
11
|
+
**Description:** Implement rails generate data_porter:target NAME [columns...] generator that scaffolds a target class in app/importers/.
|
|
12
|
+
|
|
13
|
+
**Details:**
|
|
14
|
+
|
|
15
|
+
Create lib/generators/data_porter/target_generator.rb. Arguments: name (required), columns (optional, format: name:type or name:type:required). Generate app/importers/{name}_target.rb with: class inheriting DataPorter::Target, label, model (inferred from name), sources :csv, columns block from arguments, empty persist method. Template in lib/generators/data_porter/templates/target.rb.tt.
|
|
16
|
+
|
|
17
|
+
**Test Strategy:**
|
|
18
|
+
|
|
19
|
+
Test generator creates target file with correct class name. Test column parsing (type, required flag). Test generated file is valid Ruby.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Task ID: 17
|
|
2
|
+
|
|
3
|
+
**Title:** Write RSpec test suite for core modules
|
|
4
|
+
|
|
5
|
+
**Status:** pending
|
|
6
|
+
|
|
7
|
+
**Dependencies:** 9
|
|
8
|
+
|
|
9
|
+
**Priority:** high
|
|
10
|
+
|
|
11
|
+
**Description:** Create comprehensive RSpec tests for all core modules: Configuration, StoreModels, TypeValidator, Target DSL, Registry, Sources, Orchestrator, DataImport model.
|
|
12
|
+
|
|
13
|
+
**Details:**
|
|
14
|
+
|
|
15
|
+
Set up spec/spec_helper.rb with proper requires and test configuration. Create specs: spec/data_porter/configuration_spec.rb, spec/data_porter/store_models/import_record_spec.rb, spec/data_porter/store_models/error_spec.rb, spec/data_porter/store_models/report_spec.rb, spec/data_porter/type_validator_spec.rb, spec/data_porter/target_spec.rb (test DSL), spec/data_porter/registry_spec.rb, spec/data_porter/sources/csv_spec.rb, spec/data_porter/orchestrator_spec.rb, spec/models/data_porter/data_import_spec.rb. Use fixtures or factories for test data. Mock ActiveStorage for file tests.
|
|
16
|
+
|
|
17
|
+
**Test Strategy:**
|
|
18
|
+
|
|
19
|
+
Run bundle exec rspec and verify all tests pass. Aim for high coverage of business logic.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Task ID: 18
|
|
2
|
+
|
|
3
|
+
**Title:** Implement JSON source
|
|
4
|
+
|
|
5
|
+
**Status:** pending
|
|
6
|
+
|
|
7
|
+
**Dependencies:** 8
|
|
8
|
+
|
|
9
|
+
**Priority:** medium
|
|
10
|
+
|
|
11
|
+
**Description:** Create DataPorter::Sources::Json for JSON file upload and raw JSON text input. Supports configurable json_root for nested data extraction.
|
|
12
|
+
|
|
13
|
+
**Details:**
|
|
14
|
+
|
|
15
|
+
Create lib/data_porter/sources/json.rb extending Base. fetch method: reads from attached file (download) or from config['raw_json']. Parses JSON, extracts records from json_root path (dot-separated) or root array. Transforms keys to parameterized symbols. Add _json_root DSL to Target class.
|
|
16
|
+
|
|
17
|
+
**Test Strategy:**
|
|
18
|
+
|
|
19
|
+
Test parsing from file attachment. Test parsing from raw_json config. Test json_root extraction. Test key transformation.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Task ID: 19
|
|
2
|
+
|
|
3
|
+
**Title:** Implement API source
|
|
4
|
+
|
|
5
|
+
**Status:** pending
|
|
6
|
+
|
|
7
|
+
**Dependencies:** 8
|
|
8
|
+
|
|
9
|
+
**Priority:** medium
|
|
10
|
+
|
|
11
|
+
**Description:** Create DataPorter::Sources::Api for HTTP endpoint fetching. Uses target's api_config (endpoint, headers, response_root) with params injection.
|
|
12
|
+
|
|
13
|
+
**Details:**
|
|
14
|
+
|
|
15
|
+
Create lib/data_porter/sources/api.rb extending Base. fetch method: resolves endpoint URL (lambda or string) with params from data_import.config, resolves headers (lambda or hash), makes HTTP GET with Net::HTTP, parses JSON response, extracts records from response_root, transforms keys. Add api_config DSL block to Target with endpoint, headers, response_root setters.
|
|
16
|
+
|
|
17
|
+
**Test Strategy:**
|
|
18
|
+
|
|
19
|
+
Test with mocked HTTP responses. Test endpoint lambda resolution. Test header resolution. Test response_root extraction. Test error handling for failed requests.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Task ID: 20
|
|
2
|
+
|
|
3
|
+
**Title:** Implement Dry Run mode
|
|
4
|
+
|
|
5
|
+
**Status:** pending
|
|
6
|
+
|
|
7
|
+
**Dependencies:** 9, 10
|
|
8
|
+
|
|
9
|
+
**Priority:** low
|
|
10
|
+
|
|
11
|
+
**Description:** Add dry_run! method to Orchestrator that wraps persist calls in a transaction and rolls back. Enriches ImportRecords with DB-level validation errors. Add DryRunJob and controller action.
|
|
12
|
+
|
|
13
|
+
**Details:**
|
|
14
|
+
|
|
15
|
+
Add dry_run! to Orchestrator: transitions to dry_running, wraps all persist calls in ActiveRecord::Base.transaction, catches RecordInvalid/RecordNotUnique/StandardError per record, adds DB errors to import records, raises ActiveRecord::Rollback at end, resets target_ids, transitions back to previewing, rebuilds report. Create app/jobs/data_porter/dry_run_job.rb. Add dry_run action to controller. Add dry_run_enabled DSL flag to Target. Add dry_run_passed attribute to ImportRecord StoreModel.
|
|
16
|
+
|
|
17
|
+
**Test Strategy:**
|
|
18
|
+
|
|
19
|
+
Test that dry_run! rolls back all DB changes. Test that DB validation errors are captured on records. Test status transitions (previewing -> dry_running -> previewing). Test dry_run_enabled flag controls UI button visibility.
|