pull/501/head
Ryan Arakawa 4 months ago
commit 4b462580a4

@ -0,0 +1 @@
3.4.2

@ -0,0 +1,2 @@
nodejs 18.20.4
ruby 3.1.6

@ -193,7 +193,7 @@ module Api
submitters: [[:send_email, :send_sms, :completed_redirect_url, :uuid, :name, :email, :role, submitters: [[:send_email, :send_sms, :completed_redirect_url, :uuid, :name, :email, :role,
:completed, :phone, :application_key, :external_id, :reply_to, :go_to_last, :completed, :phone, :application_key, :external_id, :reply_to, :go_to_last,
{ metadata: {}, values: {}, roles: [], readonly_fields: [], message: %i[subject body], { metadata: {}, values: {}, roles: [], readonly_fields: [], message: %i[subject body],
fields: [:name, :uuid, :default_value, :value, :title, :description, fields: [:name, :uuid, :question_id, :default_value, :value, :title, :description,
:readonly, :required, :validation_pattern, :invalid_message, :readonly, :required, :validation_pattern, :invalid_message,
{ default_value: [], value: [], preferences: {} }] }]] { default_value: [], value: [], preferences: {} }] }]]
} }

@ -237,15 +237,16 @@ module Api
:external_id, :external_id,
:shared_link, :shared_link,
{ {
external_data_fields: {},
submitters: [%i[name uuid is_requester invite_by_uuid optional_invite_by_uuid linked_to_uuid email]], submitters: [%i[name uuid is_requester invite_by_uuid optional_invite_by_uuid linked_to_uuid email]],
fields: [[:uuid, :submitter_uuid, :name, :type, fields: [[:uuid, :question_id, :submitter_uuid, :name, :type,
:required, :readonly, :default_value, :required, :readonly, :default_value,
:title, :description, :title, :description,
{ preferences: {}, { preferences: {},
conditions: [%i[field_uuid value action operation]], conditions: [%i[field_uuid value action operation]],
options: [%i[value uuid]], options: [%i[value uuid answer_id]],
validation: %i[message pattern], validation: %i[message pattern],
areas: [%i[x y w h cell_w attachment_uuid option_uuid page]] }]] areas: [%i[x y w h cell_w attachment_uuid option_uuid answer_id page]] }]]
} }
] ]

@ -0,0 +1,62 @@
# frozen_string_literal: true
require 'faraday'
class ExportController < ApplicationController
skip_authorization_check
skip_before_action :maybe_redirect_to_setup
skip_before_action :verify_authenticity_token
# Template is sent as JSON already; we're just gonnna send it on to the third party.
def export_template
export_location = ExportLocation.default_location
data = request.raw_post.present? ? JSON.parse(request.raw_post) : params.to_unsafe_h
response = post_to_api(data, export_location.templates_endpoint, export_location.extra_params)
if response&.success?
Rails.logger.info("Successfully exported template #{data[:template][:name]} to #{export_location.name}")
head :ok
else
Rails.logger.error("Failed to export template to third party: #{response&.status}")
Rollbar.error("#{export_location.name} template export API error: #{response&.status}") if defined?(Rollbar)
head :unprocessable_entity
end
rescue Faraday::Error => e
Rails.logger.error("Failed to export template Faraday: #{e.message}")
Rollbar.error("Failed to export template: #{e.message}") if defined?(Rollbar)
head :service_unavailable
rescue StandardError => e
Rails.logger.error("Failed to export template: #{e.message}")
Rollbar.error(e) if defined?(Rollbar)
head :internal_server_error
end
private
def api_connection
@api_connection ||= Faraday.new(url: ExportLocation.default_location.api_base_url) do |faraday|
faraday.request :json
faraday.response :json
faraday.adapter Faraday.default_adapter
end
rescue StandardError => e
Rails.logger.error("Failed to create API connection: #{e.message}")
Rollbar.error(e) if defined?(Rollbar)
nil
end
def post_to_api(data, endpoint, extra_params = nil)
connection = api_connection
return nil unless connection
connection.post(endpoint) do |req|
# req.headers['Authorization'] = "Bearer #{export_location.authorization_token}" lol
# Merge extra_params into data if provided
data = data.merge(extra_params) if extra_params.present? && data.is_a?(Hash)
req.body = data.is_a?(String) ? data : data.to_json
end
end
end

@ -121,14 +121,15 @@ class TemplatesController < ApplicationController
:name, :name,
{ schema: [[:attachment_uuid, :name, { conditions: [%i[field_uuid value action operation]] }]], { schema: [[:attachment_uuid, :name, { conditions: [%i[field_uuid value action operation]] }]],
submitters: [%i[name uuid is_requester linked_to_uuid invite_by_uuid optional_invite_by_uuid email]], submitters: [%i[name uuid is_requester linked_to_uuid invite_by_uuid optional_invite_by_uuid email]],
fields: [[:uuid, :submitter_uuid, :name, :type, external_data_fields: {},
fields: [[:uuid, :question_id, :submitter_uuid, :name, :type,
:required, :readonly, :default_value, :required, :readonly, :default_value,
:title, :description, :title, :description,
{ preferences: {}, { preferences: {},
conditions: [%i[field_uuid value action operation]], conditions: [%i[field_uuid value action operation]],
options: [%i[value uuid]], options: [%i[value uuid answer_id]],
validation: %i[message pattern], validation: %i[message pattern],
areas: [%i[x y w h cell_w attachment_uuid option_uuid page]] }]] } areas: [%i[x y w h cell_w attachment_uuid option_uuid answer_id page]] }]] }
) )
end end

@ -56,11 +56,45 @@ button[disabled] .enabled {
} }
.base-button { .base-button {
@apply btn btn-neutral text-white text-base; @apply btn btn-primary text-white text-base;
}
.btn-outline {
border-color: #004FCC;
color: #004FCC;
}
.btn-outline:hover {
background-color: #004FCC;
border-color: #004FCC;
color: white;
}
.btn-ghost {
color: #004FCC;
}
.btn-ghost:hover {
background-color: #004FCC;
color: white;
}
.btn-neutral {
background-color: #004FCC;
border-color: #004FCC;
color: white;
}
.btn-neutral:hover {
background-color: #0063FF;
border-color: #0063FF;
color: white;
} }
.white-button { .white-button {
@apply btn btn-outline text-base bg-white border-2; @apply btn btn-outline text-base bg-white border-2;
border-color: #004FCC;
color: #004FCC;
} }
.base-checkbox { .base-checkbox {

@ -56,11 +56,45 @@ button[disabled] .enabled {
} }
.base-button { .base-button {
@apply btn btn-neutral text-white text-base; @apply btn btn-primary text-white text-base;
}
.btn-outline {
border-color: #004FCC;
color: #004FCC;
}
.btn-outline:hover {
background-color: #004FCC;
border-color: #004FCC;
color: white;
}
.btn-ghost {
color: #004FCC;
}
.btn-ghost:hover {
background-color: #004FCC;
color: white;
}
.btn-neutral {
background-color: #004FCC;
border-color: #004FCC;
color: white;
}
.btn-neutral:hover {
background-color: #0063FF;
border-color: #0063FF;
color: white;
} }
.white-button { .white-button {
@apply btn btn-outline text-base bg-white border-2; @apply btn btn-outline text-base bg-white border-2;
border-color: #004FCC;
color: #004FCC;
} }
.base-checkbox { .base-checkbox {

@ -73,6 +73,25 @@
name="buttons" name="buttons"
/> />
<template v-else> <template v-else>
<button
class="base-button"
:class="{ disabled: isExporting }"
v-bind="isExporting ? { disabled: true } : {}"
@click.prevent="onExportClick"
>
<IconInnerShadowTop
v-if="isExporting"
width="22"
class="animate-spin"
/>
<IconDeviceFloppy
v-else
width="22"
/>
<span class="hidden md:inline">
{{ t('Export') }}
</span>
</button>
<span <span
v-if="editable" v-if="editable"
id="save_button_container" id="save_button_container"
@ -1739,6 +1758,14 @@ export default {
} }
} }
}, },
onExportClick () {
this.isExporting = true
this.export().then(() => {
window.Turbo.visit(`/templates/${this.template.id}`)
}).finally(() => {
this.isExporting = false
})
},
scrollToArea (area) { scrollToArea (area) {
const documentRef = this.documentRefs.find((a) => a.document.uuid === area.attachment_uuid) const documentRef = this.documentRefs.find((a) => a.document.uuid === area.attachment_uuid)
@ -1791,6 +1818,26 @@ export default {
this.onSave(this.template) this.onSave(this.template)
} }
}) })
},
export () {
this.baseFetch(`/export/export_template`, {
method: 'PUT',
body: JSON.stringify({
template: {
id: this.template.id,
name: this.template.name,
schema: this.template.schema[0],
submitters: this.template.submitters,
fields: this.template.fields
}
}),
headers: { 'Content-Type': 'application/json' }
}).then(() => {
console.log('exported!');
// if (this.onSave) {
// this.onSave(this.template)
// }
})
} }
} }
} }

@ -13,7 +13,7 @@
# Indexes # Indexes
# #
# index_document_generation_events_on_submitter_id (submitter_id) # index_document_generation_events_on_submitter_id (submitter_id)
# index_document_generation_events_on_submitter_id_and_event_name (submitter_id,event_name) UNIQUE WHERE ((event_name)::text = ANY ((ARRAY['start'::character varying, 'complete'::character varying])::text[])) # index_document_generation_events_on_submitter_id_and_event_name (submitter_id,event_name) UNIQUE WHERE ((event_name)::text = ANY (ARRAY[('start'::character varying)::text, ('complete'::character varying)::text]))
# #
# Foreign Keys # Foreign Keys
# #

@ -20,7 +20,7 @@
# #
# index_email_events_on_account_id_and_event_datetime (account_id,event_datetime) # index_email_events_on_account_id_and_event_datetime (account_id,event_datetime)
# index_email_events_on_email (email) # index_email_events_on_email (email)
# index_email_events_on_email_event_types (email) WHERE ((event_type)::text = ANY ((ARRAY['bounce'::character varying, 'soft_bounce'::character varying, 'complaint'::character varying, 'soft_complaint'::character varying])::text[])) # index_email_events_on_email_event_types (email) WHERE ((event_type)::text = ANY (ARRAY[('bounce'::character varying)::text, ('soft_bounce'::character varying)::text, ('complaint'::character varying)::text, ('soft_complaint'::character varying)::text]))
# index_email_events_on_emailable (emailable_type,emailable_id) # index_email_events_on_emailable (emailable_type,emailable_id)
# index_email_events_on_message_id (message_id) # index_email_events_on_message_id (message_id)
# #

@ -0,0 +1,22 @@
# == Schema Information
#
# Table name: export_locations
#
# id :bigint not null, primary key
# api_base_url :string not null
# authorization_token :string
# default_location :boolean default(FALSE), not null
# extra_params :jsonb not null
# name :string not null
# templates_endpoint :string
# created_at :datetime not null
# updated_at :datetime not null
#
class ExportLocation < ApplicationRecord
validates :name, presence: true
validates :api_base_url, presence: true
def self.default_location
where(default_location: true).first || ExportLocation.first
end
end

@ -4,22 +4,23 @@
# #
# Table name: templates # Table name: templates
# #
# id :bigint not null, primary key # id :bigint not null, primary key
# archived_at :datetime # archived_at :datetime
# fields :text not null # external_data_fields :text
# name :string not null # fields :text not null
# preferences :text not null # name :string not null
# schema :text not null # preferences :text not null
# shared_link :boolean default(FALSE), not null # schema :text not null
# slug :string not null # shared_link :boolean default(FALSE), not null
# source :text not null # slug :string not null
# submitters :text not null # source :text not null
# created_at :datetime not null # submitters :text not null
# updated_at :datetime not null # created_at :datetime not null
# account_id :integer not null # updated_at :datetime not null
# author_id :integer not null # account_id :integer not null
# external_id :string # author_id :integer not null
# folder_id :integer not null # external_id :string
# folder_id :integer not null
# #
# Indexes # Indexes
# #
@ -51,6 +52,7 @@ class Template < ApplicationRecord
attribute :preferences, :string, default: -> { {} } attribute :preferences, :string, default: -> { {} }
attribute :fields, :string, default: -> { [] } attribute :fields, :string, default: -> { [] }
attribute :schema, :string, default: -> { [] } attribute :schema, :string, default: -> { [] }
attribute :external_data_fields, :string, default: -> { {} }
attribute :submitters, :string, default: -> { [{ name: I18n.t(:first_party), uuid: SecureRandom.uuid }] } attribute :submitters, :string, default: -> { [{ name: I18n.t(:first_party), uuid: SecureRandom.uuid }] }
attribute :slug, :string, default: -> { SecureRandom.base58(14) } attribute :slug, :string, default: -> { SecureRandom.base58(14) }
attribute :source, :string, default: 'native' attribute :source, :string, default: 'native'
@ -59,6 +61,7 @@ class Template < ApplicationRecord
serialize :fields, coder: JSON serialize :fields, coder: JSON
serialize :schema, coder: JSON serialize :schema, coder: JSON
serialize :submitters, coder: JSON serialize :submitters, coder: JSON
serialize :external_data_fields, coder: JSON
has_many_attached :documents has_many_attached :documents

@ -40,15 +40,6 @@
</div> </div>
<% if !template.archived_at? %> <% if !template.archived_at? %>
<div class="flex items-center space-x-2"> <div class="flex items-center space-x-2">
<% if can?(:update, template) %>
<div class="tooltip" data-tip="<%= t('preferences') %>">
<%= link_to template_preferences_path(template), class: 'btn border border-base-200 bg-base-200 hover:bg-base-300 hover:border-base-300 btn-sm flex-1 hidden md:flex', data: { turbo_frame: :modal } do %>
<span class="flex items-center justify-center space-x-2">
<%= svg_icon('adjustments_horizontal', class: 'w-6 h-6') %>
</span>
<% end %>
</div>
<% end %>
<%= link_to template_share_link_path(template), class: 'absolute md:relative bottom-0 right-0 btn btn-xs md:btn-sm whitespace-nowrap btn-neutral text-white mt-1 px-2', data: { turbo_frame: :modal } do %> <%= link_to template_share_link_path(template), class: 'absolute md:relative bottom-0 right-0 btn btn-xs md:btn-sm whitespace-nowrap btn-neutral text-white mt-1 px-2', data: { turbo_frame: :modal } do %>
<span class="flex items-center justify-center space-x-2"> <span class="flex items-center justify-center space-x-2">
<%= svg_icon('link', class: 'w-4 h-4 md:w-6 md:h-6 text-white') %> <%= svg_icon('link', class: 'w-4 h-4 md:w-6 md:h-6 text-white') %>

@ -852,6 +852,11 @@ en: &en
items: items:
range_with_total: "%{from}-%{to} of %{count} items" range_with_total: "%{from}-%{to} of %{count} items"
range_without_total: "%{from}-%{to} items" range_without_total: "%{from}-%{to} items"
exports:
templates:
error: Error exporting template
api_error: Error with remote endpoint
success: Template exported successfully
es: &es es: &es
authenticate_embedded_form_preview_with_token: Autenticar vista previa del formulario incrustado con token authenticate_embedded_form_preview_with_token: Autenticar vista previa del formulario incrustado con token

@ -58,6 +58,13 @@ Rails.application.routes.draw do
end end
end end
resources :export, controller: 'export' do
collection do
put :export_template
put :export_template_by_id
end
end
resources :verify_pdf_signature, only: %i[create] resources :verify_pdf_signature, only: %i[create]
resource :mfa_setup, only: %i[show new edit create destroy], controller: 'mfa_setup' resource :mfa_setup, only: %i[show new edit create destroy], controller: 'mfa_setup'
resources :account_configs, only: %i[create destroy] resources :account_configs, only: %i[create destroy]

@ -0,0 +1,14 @@
class CreateExportLocations < ActiveRecord::Migration[8.0]
def change
create_table :export_locations do |t|
t.string :name, null: false
t.boolean :default_location, null: false, default: false
t.string :authorization_token
t.string :api_base_url, null: false
# t.string extra_params, null: false, default: '{}'
t.string :templates_endpoint
# t.string other_export_type_endpoints_maybe_one_day
t.timestamps
end
end
end

@ -0,0 +1,5 @@
class AddAuthParamsToExportLocations < ActiveRecord::Migration[8.0]
def change
add_column :export_locations, :extra_params, :jsonb, null: false, default: {}
end
end

@ -0,0 +1,5 @@
class AddExternalDataFieldsToTemplates < ActiveRecord::Migration[8.0]
def change
add_column :templates, :external_data_fields, :text
end
end

@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[8.0].define(version: 2025_06_18_085322) do ActiveRecord::Schema[8.0].define(version: 2025_07_03_143236) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "btree_gin" enable_extension "btree_gin"
enable_extension "pg_catalog.plpgsql" enable_extension "pg_catalog.plpgsql"
@ -160,7 +160,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_06_18_085322) do
t.string "event_name", null: false t.string "event_name", null: false
t.datetime "created_at", null: false t.datetime "created_at", null: false
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
t.index ["submitter_id", "event_name"], name: "index_document_generation_events_on_submitter_id_and_event_name", unique: true, where: "((event_name)::text = ANY ((ARRAY['start'::character varying, 'complete'::character varying])::text[]))" t.index ["submitter_id", "event_name"], name: "index_document_generation_events_on_submitter_id_and_event_name", unique: true, where: "((event_name)::text = ANY (ARRAY[('start'::character varying)::text, ('complete'::character varying)::text]))"
t.index ["submitter_id"], name: "index_document_generation_events_on_submitter_id" t.index ["submitter_id"], name: "index_document_generation_events_on_submitter_id"
end end
@ -177,7 +177,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_06_18_085322) do
t.datetime "created_at", null: false t.datetime "created_at", null: false
t.index ["account_id", "event_datetime"], name: "index_email_events_on_account_id_and_event_datetime" t.index ["account_id", "event_datetime"], name: "index_email_events_on_account_id_and_event_datetime"
t.index ["email"], name: "index_email_events_on_email" t.index ["email"], name: "index_email_events_on_email"
t.index ["email"], name: "index_email_events_on_email_event_types", where: "((event_type)::text = ANY ((ARRAY['bounce'::character varying, 'soft_bounce'::character varying, 'complaint'::character varying, 'soft_complaint'::character varying])::text[]))" t.index ["email"], name: "index_email_events_on_email_event_types", where: "((event_type)::text = ANY (ARRAY[('bounce'::character varying)::text, ('soft_bounce'::character varying)::text, ('complaint'::character varying)::text, ('soft_complaint'::character varying)::text]))"
t.index ["emailable_type", "emailable_id"], name: "index_email_events_on_emailable" t.index ["emailable_type", "emailable_id"], name: "index_email_events_on_emailable"
t.index ["message_id"], name: "index_email_events_on_message_id" t.index ["message_id"], name: "index_email_events_on_message_id"
end end
@ -216,6 +216,17 @@ ActiveRecord::Schema[8.0].define(version: 2025_06_18_085322) do
t.index ["user_id"], name: "index_encrypted_user_configs_on_user_id" t.index ["user_id"], name: "index_encrypted_user_configs_on_user_id"
end end
create_table "export_locations", force: :cascade do |t|
t.string "name", null: false
t.boolean "default_location", default: false, null: false
t.string "authorization_token"
t.string "api_base_url", null: false
t.string "templates_endpoint"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.jsonb "extra_params", default: {}, null: false
end
create_table "oauth_access_grants", force: :cascade do |t| create_table "oauth_access_grants", force: :cascade do |t|
t.integer "resource_owner_id", null: false t.integer "resource_owner_id", null: false
t.integer "application_id", null: false t.integer "application_id", null: false
@ -382,6 +393,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_06_18_085322) do
t.string "external_id" t.string "external_id"
t.text "preferences", null: false t.text "preferences", null: false
t.boolean "shared_link", default: false, null: false t.boolean "shared_link", default: false, null: false
t.text "external_data_fields"
t.index ["account_id", "folder_id", "id"], name: "index_templates_on_account_id_and_folder_id_and_id", where: "(archived_at IS NULL)" t.index ["account_id", "folder_id", "id"], name: "index_templates_on_account_id_and_folder_id_and_id", where: "(archived_at IS NULL)"
t.index ["account_id", "id"], name: "index_templates_on_account_id_and_id_archived", where: "(archived_at IS NOT NULL)" t.index ["account_id", "id"], name: "index_templates_on_account_id_and_id_archived", where: "(archived_at IS NOT NULL)"
t.index ["account_id"], name: "index_templates_on_account_id" t.index ["account_id"], name: "index_templates_on_account_id"

@ -7,15 +7,17 @@ module.exports = {
{ {
docuseal: { docuseal: {
'color-scheme': 'light', 'color-scheme': 'light',
primary: '#e4e0e1', primary: '#004FCC',
link: '#004FCC',
secondary: '#ef9fbc', secondary: '#ef9fbc',
accent: '#eeaf3a', accent: '#eeaf3a',
neutral: '#291334', neutral: '#291334',
'base-100': '#f2f2f3', 'base-100': '#f2f2f3',
'base-200': '#efeae6', 'base-200': '#efeae6',
'base-300': '#e7e2df', 'base-300': '#e7e2df',
'base-content': '#291334', 'base-content': 'rgb(73, 75, 80)',
'--rounded-btn': '1.9rem', 'font-size': '16px',
'--rounded-btn': '4px',
'--tab-border': '2px', '--tab-border': '2px',
'--tab-radius': '.5rem' '--tab-radius': '.5rem'
} }

Loading…
Cancel
Save