mirror of
https://github.com/usetrmnl/terminus.git
synced 2026-08-13 14:29:27 -07:00
Added designs import resource
Necessary to import an exported (zip file) design. The full implementation is included as a single commit to capture the details of how this works. Here's the breakdown: * *Import Action*: Creates a new screen template based on the model ID and associated zip file content that is uploaded. * *Import Partial*: The `_import` partial provides the form within a popover via the index page to collect details from the user. This is also why the `index` template has been updated to link to the popover and also why the index view supplies the models. In the future, reduce the responsibility of loading the models on the index view and use a import new action instead. Milestone: minor
This commit is contained in:
@@ -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
|
||||
@@ -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| %>
|
||||
|
||||
<p>Please attach a zip file to import a previously exported design into your collection.</p>
|
||||
|
||||
<%= 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" %>
|
||||
|
||||
<div class="form-actions">
|
||||
<%= 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" %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
@@ -15,10 +15,22 @@
|
||||
|
||||
<div class="bit-actions">
|
||||
<%= 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) %>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<%= render :import, models: %>
|
||||
|
||||
<%= scope(:popover_default_content, name: :overview, label: "Overview").render do %>
|
||||
<p>Use to manage templates for building screens. Each template can have multiple screens associated with it.</p>
|
||||
|
||||
@@ -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 %>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user