diff --git a/app/actions/designs/import/create.rb b/app/actions/designs/import/create.rb new file mode 100644 index 00000000..93878ff4 --- /dev/null +++ b/app/actions/designs/import/create.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +require "dry/monads" + +module Terminus + module Actions + module Designs + module Import + # The create action. + class Create < Action + include Deps["aspects.designs.importer", "aspects.screens.upserter"] + include Dry::Monads[:result] + + contract Contracts::Designs::Import + + def handle request, response + parameters = request.params + + halt :unprocessable_content unless parameters.valid? + + process parameters, response + end + + private + + def process parameters, response + flash = response.flash + + case upsert parameters + in Success then flash[:notice] = "Design imported!" + in Failure(message) then flash[:alert] = message + end + + response.redirect_to routes.path(:designs) + end + + def upsert parameters + model_id, design = parameters.to_h.values_at :model_id, :design + + importer.call(design.dig(:attachment, :tempfile)) + .bind do |screen_template| + upserter.call(model_id:, **screen_template.screen_attributes) + end + end + end + end + end + end +end diff --git a/app/templates/designs/_import.html.erb b/app/templates/designs/_import.html.erb new file mode 100644 index 00000000..9c884aec --- /dev/null +++ b/app/templates/designs/_import.html.erb @@ -0,0 +1,38 @@ +<%= scope(:popover_default_content, name: :import, label: "Import").render do %> + <%= form_for :design, + routes.path(:design_import), + enctype: "multipart/form-data", + class: "bit-form" do |form| %> + +

Please attach a zip file to import a previously exported design into your collection.

+ + <%= tag.select id: :model_id, name: :model_id, class: :value do %> + <%= tag.option "Select model...", value: nil %> + + <% models.each do |model| %> + <%= tag.option model.label, value: model.id %> + <% end %> + <% end %> + + <%= form.file_field :attachment, + id: :design_attachment, + value: nil, + class: :value, + accept: "application/zip" %> + +
+ <%= form.submit "Submit", + class: "bit-button-accept", + **HTMX[ + post: routes.path(:design_import), + encoding: "multipart/form-data", + trigger: :click, + target: ".site-body", + select: ".site-body", + disabled_elt: "this" + ] %> + + <%= link_to "Cancel", routes.path(:designs), class: "bit-button-decline" %> +
+ <% end %> +<% end %> diff --git a/app/templates/designs/index.html.erb b/app/templates/designs/index.html.erb index b365d8e1..1e02c5c6 100644 --- a/app/templates/designs/index.html.erb +++ b/app/templates/designs/index.html.erb @@ -15,10 +15,22 @@
<%= render "shared/actions/search", path: routes.path(:designs), query: %> + + <%= scope( + :popover_trigger_button, + icon: :upload, + tip: :import, + target: :import, + width: 25, + height: 25 + ).render %> + <%= render "shared/actions/new", path: routes.path(:design_new) %>
+ <%= render :import, models: %> + <%= scope(:popover_default_content, name: :overview, label: "Overview").render do %>

Use to manage templates for building screens. Each template can have multiple screens associated with it.

@@ -39,6 +51,7 @@ <%= scope(:tooltip_action_content, label: "Info").render %> <%= scope(:tooltip_action_content, label: "New").render %> + <%= scope(:tooltip_action_content, label: "Import").render %> <%= scope(:tooltip_action_content, label: "Search").render %> <%= scope(:tooltip_action_content, label: "Clear").render %> <%= scope(:tooltip_action_content, label: "View").render %> diff --git a/app/views/designs/index.rb b/app/views/designs/index.rb index 5df5c61f..cff24fc8 100644 --- a/app/views/designs/index.rb +++ b/app/views/designs/index.rb @@ -5,6 +5,9 @@ module Terminus module Designs # The index view. class Index < View + include Deps[model_repository: "repositories.model"] + + expose(:models) { model_repository.all } expose :templates expose :query end diff --git a/config/routes.rb b/config/routes.rb index 38b79889..95a4f58c 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -85,6 +85,7 @@ module Terminus delete "/designs/:id", to: "designs.delete", as: :design get "/designs/:design_id/export", to: "designs.export.show", as: :design_export + post "/designs/import", to: "designs.import.create", as: :design_import get "/extensions", to: "extensions.index", as: :extensions get "/extensions/new", to: "extensions.new", as: :extension_new diff --git a/spec/app/actions/designs/import/create_spec.rb b/spec/app/actions/designs/import/create_spec.rb new file mode 100644 index 00000000..01fd69f7 --- /dev/null +++ b/spec/app/actions/designs/import/create_spec.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +require "hanami_helper" + +RSpec.describe Terminus::Actions::Designs::Import::Create, :db do + subject(:action) { described_class.new } + + describe "#call" do + let(:exporter) { Terminus::Aspects::Designs::Exporter.new } + let(:model) { Factory[:model] } + let(:screen_template) { Factory.structs[:screen_template] } + + it "renders errors when invalid" do + allow(screen_template).to receive(:export_attributes).and_return({}) + + response = action.call Rack::MockRequest.env_for( + "", + "router.params" => { + model_id: model.id, + design: { + attachment: { + name: "test", + type: "application/zip", + head: "test", + filename: "test.zip", + tempfile: exporter.call(screen_template).value! + } + } + } + ) + + expect(response.flash.inspect).to include("label is missing") + end + + it "flashs success when valid" do + response = action.call Rack::MockRequest.env_for( + "", + "router.params" => { + model_id: model.id, + design: { + attachment: { + name: "test", + type: "application/zip", + head: "test", + filename: "test.zip", + tempfile: exporter.call(screen_template).value! + } + } + } + ) + + expect(response.flash.inspect).to include("Design imported!") + end + + it "answers unprocessable content when parameters are missing" do + response = Rack::MockRequest.new(action).post("") + expect(response.status).to eq(422) + end + end +end diff --git a/spec/features/designs_spec.rb b/spec/features/designs_spec.rb index 47fe0d15..e175f284 100644 --- a/spec/features/designs_spec.rb +++ b/spec/features/designs_spec.rb @@ -3,6 +3,8 @@ require "hanami_helper" RSpec.describe "Designs", :db do + using Refinements::Pathname + let(:model) { Factory[:model] } let(:template) { Factory[:screen_template] } @@ -30,6 +32,27 @@ RSpec.describe "Designs", :db do expect(page).to have_text("Edit Design") end + it "imports", :aggregate_failures, :js do + model + exporter = Terminus::Aspects::Designs::Exporter.new + importer = Terminus::Aspects::Designs::Importer.new + screen_template = Factory.structs[:screen_template, label: "Design Import Test"] + path = exporter.call(screen_template).bind { |io| temp_dir.join("test.zip").write io.read } + + path.open { importer.call it } + + visit routes.path(:designs) + click_button "Upload" + + within ".bit-popover-content", text: "Import" do + select model.label, from: "model_id" + attach_file "design_attachment", path + click_button "Submit" + end + + expect(page).to have_text("Design Import Test") + end + it "exports" do visit routes.path(:designs) click_link "Download"