Merge pull request #568 from ZmagoD/zd_SCI_1119

Add images to fields with rich text formatting
This commit is contained in:
Zmago Devetak 2017-05-08 13:06:58 +02:00 committed by GitHub
commit 4b816f5322
29 changed files with 543 additions and 59 deletions

View file

@ -65,7 +65,7 @@ gem 'aws-sdk-v1'
gem 'delayed_job_active_record'
gem 'devise-async'
gem 'ruby-graphviz', '~> 1.2' # Graphviz for rails
gem 'tinymce-rails' # Rich text editor
gem 'tinymce-rails', '~> 4.5.7' # Rich text editor
gem 'base62' # Used for smart annotations
gem 'newrelic_rpm'

View file

@ -310,7 +310,7 @@ GEM
thor (0.19.1)
thread_safe (0.3.5)
tilt (2.0.1)
tinymce-rails (4.5.2)
tinymce-rails (4.5.7)
railties (>= 3.1.1)
turbolinks (2.5.3)
coffee-rails
@ -398,7 +398,7 @@ DEPENDENCIES
sneaky-save!
spinjs-rails
starscope
tinymce-rails
tinymce-rails (~> 4.5.7)
turbolinks
tzinfo-data
uglifier (>= 1.3.0)

View file

@ -543,12 +543,15 @@ $("[data-action='new-step']").on("ajax:success", function(e, data) {
applyCancelOnNew();
toggleButtons(false);
initializeCheckboxSorting();
TinyMCE.init();
$("#step_name").focus();
$("#new-step-main-tab a").on("shown.bs.tab", function() {
$("#step_name").focus();
});
TinyMCE.refresh();
});
// Needed because server-side validation failure clears locations of

View file

@ -18,13 +18,15 @@ var TinyMCE = (function() {
init : function() {
if (typeof tinyMCE != 'undefined') {
tinyMCE.init({
selector: "textarea.tinymce",
toolbar: ["undo redo | insert | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link | forecolor backcolor | codesample"],
plugins: "link,advlist,codesample,autolink,lists,charmap,hr,anchor,searchreplace,wordcount,visualblocks,visualchars,insertdatetime,nonbreaking,save,contextmenu,directionality,paste,textcolor,colorpicker,textpattern,imagetools",
selector: 'textarea.tinymce',
toolbar: ["undo redo | insert | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link | forecolor backcolor | customimageuploader | codesample"],
plugins: "customimageuploader,link,advlist,codesample,autolink,lists,charmap,hr,anchor,searchreplace,wordcount,visualblocks,visualchars,insertdatetime,nonbreaking,save,contextmenu,directionality,paste,textcolor,colorpicker,textpattern",
codesample_languages: [{"text":"R","value":"r"},{"text":"MATLAB","value":"matlab"},{"text":"Python","value":"python"},{"text":"JSON","value":"javascript"},{"text":"HTML/XML","value":"markup"},{"text":"JavaScript","value":"javascript"},{"text":"CSS","value":"css"},{"text":"PHP","value":"php"},{"text":"Ruby","value":"ruby"},{"text":"Java","value":"java"},{"text":"C","value":"c"},{"text":"C#","value":"csharp"},{"text":"C++","value":"cpp"}],
removed_menuitems: 'newdocument',
object_resizing: false,
elementpath: false,
default_link_target: "_blank",
forced_root_block: false,
default_link_target: '_blank',
target_list: [
{title: 'New page', value: '_blank'},
{title: 'Same page', value: '_self'}

View file

@ -0,0 +1,245 @@
(function() {
'use strict';
tinymce.PluginManager.requireLangPack('customimageuploader');
tinymce.create('tinymce.plugins.CustomImageUploader', {
CustomImageUploader: function(ed, url) {
var form, iframe, win, throbber, editor = ed;
function showDialog() {
win = editor.windowManager.open({
title: "<%= I18n.t 'tiny_mce.upload_window_title' %>",
width: 520 + parseInt(editor.getLang(
'customimageuploader.delta_width', 0
), 10),
height: 180 + parseInt(editor.getLang(
'customimageuploader.delta_height', 0
), 10),
body: [
{type: 'iframe', url: 'javascript:void(0)'},
{type: 'textbox',
name: 'file',
label: "<%= I18n.t 'tiny_mce.upload_window_label' %>",
subtype: 'file'},
{type: 'container',
classes: 'error',
html: "<p style='color: #b94a48;'>&nbsp;</p>"},
{type: 'container', classes: 'throbber'},
],
buttons: [
{text: "<%= I18n.t 'tiny_mce.insert_btn' %>",
onclick: insertImage,
subtype: 'primary'},
{text: "<%= I18n.t 'general.cancel' %>",
onclick: editor.windowManager.close}
],
}, {
plugin_url: url
});
win.off('submit');
win.on('submit', insertImage);
iframe = win.find('iframe')[0];
form = createElement('form', {
action: editor.getParam('customimageuploader_form_url',
'<%= Rails.application
.routes
.url_helpers
.tiny_mce_assets_path %>'),
target: iframe._id,
method: 'POST',
enctype: 'multipart/form-data',
accept_charset: 'UTF-8',
});
iframe.getEl().name = iframe._id;
// Create some needed hidden inputs
form.appendChild(createElement('input',
{type: 'hidden',
name: 'utf8',
value: '✓'}));
form.appendChild(createElement('input',
{type: 'hidden',
name: 'authenticity_token',
value: getMetaContents('csrf-token')}));
form.appendChild(createElement('input',
{type: 'hidden',
name: 'object_type',
value: $(editor.getElement())
.data('object-type')}));
form.appendChild(createElement('input',
{type: 'hidden',
name: 'object_id',
value: $(editor.getElement())
.data('object-id')}));
form.appendChild(createElement('input',
{type: 'hidden',
name: 'hint',
value: editor.getParam('uploadimage_hint', '')}));
var el = win.getEl();
var body = document.getElementById(el.id + '-body');
// Copy everything TinyMCE made into our form
var containers = body.getElementsByClassName('mce-container');
for(var i = 0; i < containers.length; i++) {
form.appendChild(containers[i]);
}
var inputs = form.getElementsByTagName('input');
for(var i = 0; i < inputs.length; i++) {
var ctrl = inputs[i];
if(ctrl.tagName.toLowerCase() == 'input' && ctrl.type != 'hidden') {
if(ctrl.type == 'file') {
ctrl.name = 'file';
tinymce.DOM.setStyles(ctrl, {
'border': 0,
'boxShadow': 'none',
'webkitBoxShadow': 'none',
});
} else {
ctrl.name = 'alt';
}
}
}
body.appendChild(form);
}
function insertImage() {
if(getInputValue('file') == '') {
return handleError("<%= I18n.t 'tiny_mce.error_message' %>");
}
throbber = new top.tinymce.ui.Throbber(win.getEl());
throbber.show();
clearErrors();
/* Add event listeners.
* We remove the existing to avoid them being called twice in case
* of errors and re-submitting afterwards.
*/
var target = iframe.getEl();
if(target.attachEvent) {
target.detachEvent('onload', uploadDone);
target.attachEvent('onload', uploadDone);
} else {
target.removeEventListener('load', uploadDone);
target.addEventListener('load', uploadDone, false);
}
form.submit();
}
function uploadDone() {
if(throbber) {
throbber.hide();
}
var target = iframe.getEl();
if(target.document || target.contentDocument) {
var doc = target.contentDocument || target.contentWindow.document;
handleResponse(doc.getElementsByTagName("body")[0].innerHTML);
} else {
handleError("<%= I18n.t 'tiny_mce.server_not_respond' %>");
}
}
function handleResponse(ret) {
try {
var json = tinymce.util.JSON.parse(ret);
if(json['error']) {
handleError(json['error']['message']);
} else {
editor.execCommand('mceInsertContent', false, buildHTML(json));
editor.windowManager.close();
}
} catch(e) {
// hack that gets the server error message
var error_json = JSON.parse($(ret).text());
handleError(error_json['error'][0]);
}
}
function clearErrors() {
var message = win.find('.error')[0].getEl();
if(message)
message.getElementsByTagName('p')[0].innerHTML = '&nbsp;';
}
function handleError(error) {
var message = win.find('.error')[0].getEl();
if(message) {
message
.getElementsByTagName('p')[0]
.innerHTML = editor.translate(error);
}
}
function createElement(element, attributes) {
var el = document.createElement(element);
for(var property in attributes) {
if (!(attributes[property] instanceof Function)) {
el[property] = attributes[property];
}
}
return el;
}
function buildHTML(json) {
var imgstr = "<img src='" + json['image']['url'] + "'";
imgstr += " data-token='" + json['image']['token'] + "'"
imgstr += " alt='description-" + json['image']['token'] + "'/>";
return imgstr;
}
function getInputValue(name) {
var inputs = form.getElementsByTagName('input');
for(var i in inputs)
if(inputs[i].name == name)
return inputs[i].value;
return "";
}
function getMetaContents(mn) {
var m = document.getElementsByTagName('meta');
for(var i in m)
if(m[i].name == mn)
return m[i].content;
return null;
}
// Add a button that opens a window
editor.addButton('customimageuploader', {
tooltip: "<%= I18n.t 'tiny_mce.upload_window_label' %>",
icon: 'image',
onclick: showDialog
});
// Adds a menu item to the tools menu
editor.addMenuItem('customimageuploader', {
text: "<%= I18n.t 'tiny_mce.upload_window_label' %>",
icon: 'image',
context: 'insert',
onclick: showDialog
});
}
});
tinymce.PluginManager.add('customimageuploader',
tinymce.plugins.CustomImageUploader);
})();

View file

@ -2,6 +2,7 @@ class ResultTextsController < ApplicationController
include ResultsHelper
include ActionView::Helpers::UrlHelper
include ApplicationHelper
include TinyMceHelper
include Rails.application.routes.url_helpers
before_action :load_vars, only: [:edit, :update, :download]
@ -31,6 +32,8 @@ class ResultTextsController < ApplicationController
def create
@result_text = ResultText.new(result_params[:result_text_attributes])
# gerate a tag that replaces img tag in database
@result_text.text = parse_tiny_mce_asset_to_token(@result_text.text)
@result = Result.new(
user: current_user,
my_module: @my_module,
@ -40,7 +43,9 @@ class ResultTextsController < ApplicationController
@result.last_modified_by = current_user
respond_to do |format|
if (@result.save and @result_text.save) then
if @result.save && @result_text.save
# link tiny_mce_assets to the text result
link_tiny_mce_assets(@result_text.text, @result_text)
result_annotation_notification
# Generate activity
@ -82,6 +87,7 @@ class ResultTextsController < ApplicationController
end
def edit
@result_text.text = generate_image_tag_from_token(@result_text.text)
respond_to do |format|
format.json {
render json: {
@ -98,6 +104,8 @@ class ResultTextsController < ApplicationController
update_params = result_params
@result.last_modified_by = current_user
@result.assign_attributes(update_params)
@result_text.text = parse_tiny_mce_asset_to_token(@result_text.text,
@result_text)
success_flash = t("result_texts.update.success_flash",
module: @my_module.name)
if @result.archived_changed?(from: false, to: true)

View file

@ -1,6 +1,7 @@
class StepsController < ApplicationController
include ActionView::Helpers::TextHelper
include ApplicationHelper
include TinyMceHelper
include StepsActions
before_action :load_vars, only: [:edit, :update, :destroy, :show]
@ -28,7 +29,8 @@ class StepsController < ApplicationController
def create
@step = Step.new(step_params)
# gerate a tag that replaces img tag in database
@step.description = parse_tiny_mce_asset_to_token(@step.description)
@step.completed = false
@step.position = @protocol.number_of_steps
@step.protocol = @protocol
@ -57,7 +59,11 @@ class StepsController < ApplicationController
asset.post_process_file(@protocol.team)
end
# link tiny_mce_assets to the step
link_tiny_mce_assets(@step.description, @step)
create_annotation_notifications(@step)
# Generate activity
if @protocol.in_module?
Activity.create(
@ -120,6 +126,7 @@ class StepsController < ApplicationController
end
def edit
@step.description = generate_image_tag_from_token(@step.description)
respond_to do |format|
format.json do
render json: {
@ -159,6 +166,12 @@ class StepsController < ApplicationController
table.team = current_team
end
# gerate a tag that replaces img tag in databases
@step.description = parse_tiny_mce_asset_to_token(
params[:step][:description],
@step
)
if @step.save
@step.reload

View file

@ -0,0 +1,31 @@
class TinyMceAssetsController < ApplicationController
before_action :find_object
def create
image = params.fetch(:file) { render_404 }
tiny_img = TinyMceAsset.new(image: image,
reference: @obj,
team_id: current_team.id)
if tiny_img.save
render json: {
image: {
url: view_context.image_url(tiny_img.url(:large)),
token: Base62.encode(tiny_img.id)
}
}, content_type: 'text/html'
else
render json: {
error: tiny_img.errors.full_messages
}, status: :unprocessable_entity
end
end
private
def find_object
obj_type = params.fetch(:object_type) { render_404 }
obj_id = params.fetch(:object_id) { render_404 }
render_404 unless %w(step result_text).include? obj_type
@obj = obj_type.classify.constantize.find_by_id(obj_id)
end
end

View file

@ -130,9 +130,8 @@ class SampleDatatable < AjaxDatatablesRails::Base
# Add custom attributes
record.sample_custom_fields.each do |scf|
sample[@cf_mappings[scf.custom_field_id]] = custom_auto_link(scf.value,
true,
@team)
sample[@cf_mappings[scf.custom_field_id]] =
custom_auto_link(scf.value, simple_format: true, team: @team)
end
sample
end

View file

@ -103,6 +103,9 @@ module ApplicationHelper
end
def smart_annotation_parser(text, team = nil)
# sometimes happens that the "team" param gets wrong data: "{nil, []}"
# so we have to check if the "team" param is kind of Team object
team = nil unless team.is_a? Team
new_text = smart_annotation_filter_resources(text)
new_text = smart_annotation_filter_users(new_text, team)
new_text
@ -187,10 +190,9 @@ module ApplicationHelper
if user &&
team &&
UserTeam.user_in_team(user, team).any?
user_t, = user
.user_teams
.where('user_teams.team_id = ?', team)
.first
user_t = user.user_teams
.where('user_teams.team_id = ?', team)
.first
end
user_description = %(<div class='col-xs-4'>
<img src='#{user_avatar_absolute_url(user, :thumb)}'

View file

@ -15,14 +15,18 @@ module InputSanitizeHelper
ERB::Util.html_escape(text)
end
def custom_auto_link(text, simple_format = true, org = nil, wrapper_tag = {})
def custom_auto_link(text, options = {})
simple_format = options.fetch(:simple_format) { true }
team = options.fetch(:team) { nil },
wrapper_tag = options.fetch(:wrapper_tag) { {} }
tags = options.fetch(:tags) { [] }
text = if simple_format
simple_format(sanitize_input(text), {}, wrapper_tag)
else
sanitize_input(text)
sanitize_input(text, tags)
end
auto_link(
smart_annotation_parser(text, org),
smart_annotation_parser(text, team),
link: :urls,
sanitize: false,
html: { target: '_blank' }

View file

@ -0,0 +1,47 @@
module TinyMceHelper
def parse_tiny_mce_asset_to_token(text, ref = nil)
ids = []
html = Nokogiri::HTML(text)
html.search('img').each do |img|
next unless img['data-token']
img_id = Base62.decode(img['data-token'])
ids << img_id
token = "[~tiny_mce_id:#{img_id}]"
img.replace(token)
next unless ref
tiny_img = TinyMceAsset.find_by_id(img_id)
tiny_img.reference = ref unless tiny_img.step || tiny_img.result_text
tiny_img.save
end
destroy_removed_tiny_mce_assets(ids, ref) if ref
html
end
def generate_image_tag_from_token(text)
regex = /\[~tiny_mce_id:([0-9a-zA-Z]+)\]/
text.gsub(regex) do |el|
match = el.match(regex)
img = TinyMceAsset.find_by_id(match[1])
next unless img
image_tag img.url, data: { token: Base62.encode(img.id) }
end
end
def link_tiny_mce_assets(text, ref)
ids = []
regex = /\[~tiny_mce_id:([0-9a-zA-Z]+)\]/
text.gsub(regex) do |img|
match = img.match(regex)
tiny_img = TinyMceAsset.find_by_id(match[1])
next unless tiny_img
ids << tiny_img.id
tiny_img.public_send("#{ref.class.to_s.underscore}=".to_sym, ref)
tiny_img.save!
end
destroy_removed_tiny_mce_assets(ids, ref)
end
def destroy_removed_tiny_mce_assets(ids, ref)
ref.tiny_mce_assets.where.not('id IN (?)', ids).destroy_all
end
end

View file

@ -4,6 +4,6 @@ class ResultText < ActiveRecord::Base
presence: true,
length: { maximum: Constants::RICH_TEXT_MAX_LENGTH }
validates :result, presence: true
belongs_to :result, inverse_of: :result_text
has_many :tiny_mce_assets, inverse_of: :result_text, dependent: :destroy
end

View file

@ -25,6 +25,7 @@ class Step < ActiveRecord::Base
has_many :tables, through: :step_tables
has_many :report_elements, inverse_of: :step,
dependent: :destroy
has_many :tiny_mce_assets, inverse_of: :step, dependent: :destroy
accepts_nested_attributes_for :checklists,
reject_if: :all_blank,

View file

@ -24,7 +24,7 @@ class Team < ActiveRecord::Base
has_many :custom_fields, inverse_of: :team
has_many :protocols, inverse_of: :team, dependent: :destroy
has_many :protocol_keywords, inverse_of: :team, dependent: :destroy
has_many :tiny_mce_assets, inverse_of: :team, dependent: :destroy
# Based on file's extension opens file (used for importing)
def self.open_spreadsheet(file)
filename = file.original_filename
@ -268,10 +268,13 @@ class Team < ActiveRecord::Base
project.project_my_modules.find_each do |my_module|
my_module.protocol.steps.find_each do |step|
step.assets.find_each { |asset| st += asset.estimated_size }
step.tiny_mce_assets.find_each { |tiny| st += tiny.estimated_size }
end
my_module.results.find_each do |result|
if result.is_asset then
st += result.asset.estimated_size
st += result.asset.estimated_size if result.is_asset
if result.is_text
tiny_assets = TinyMceAsset.where(result_text: result.result_text)
tiny_assets.find_each { |tiny| st += tiny.estimated_size }
end
end
end

View file

@ -0,0 +1,78 @@
class TinyMceAsset < ActiveRecord::Base
attr_accessor :reference
before_create :set_reference
after_create :update_estimated_size
after_destroy :release_team_space
belongs_to :team, inverse_of: :tiny_mce_assets
belongs_to :step, inverse_of: :tiny_mce_assets
belongs_to :result_text, inverse_of: :tiny_mce_assets
has_attached_file :image,
styles: { large: [Constants::LARGE_PIC_FORMAT, :jpg] },
convert_options: { large: '-quality 100 -strip' }
validates_attachment_content_type :image,
content_type: %r{^image/#{ Regexp.union(
Constants::WHITELISTED_IMAGE_TYPES
) }}
validates_attachment :image,
presence: true,
size: {
less_than: Constants::FILE_MAX_SIZE_MB.megabytes
}
validates :estimated_size, presence: true
# When using S3 file upload, we can limit file accessibility with url signing
def presigned_url(style = :large,
download: false,
timeout: Constants::URL_SHORT_EXPIRE_TIME)
if stored_on_s3?
if download
download_arg = 'attachment; filename=' + URI.escape(image_file_name)
else
download_arg = nil
end
signer = Aws::S3::Presigner.new(client: S3_BUCKET.client)
signer.presigned_url(:get_object,
bucket: S3_BUCKET.name,
key: image.path(style)[1..-1],
expires_in: timeout,
response_content_disposition: download_arg)
end
end
def stored_on_s3?
image.options[:storage].to_sym == :s3
end
def url(style = :large, timeout: Constants::URL_SHORT_EXPIRE_TIME)
if image.is_stored_on_s3?
presigned_url(style, timeout: timeout)
else
image.url(style)
end
end
private
def update_estimated_size
return if image_file_size.blank?
es = image_file_size * Constants::ASSET_ESTIMATED_SIZE_FACTOR
update(estimated_size: es)
Rails.logger.info "Asset #{id}: Estimated size successfully calculated"
# update team space taken
self.team.take_space(es)
self.team.save
end
def release_team_space
self.team.release_space(estimated_size)
self.team.save
end
def set_reference
obj_type = "#{@reference.class.to_s.underscore}=".to_sym
public_send(obj_type, @reference) if @reference
end
end

View file

@ -23,7 +23,9 @@
<div class="report-element-body">
<div class="row">
<div class="col-xs-12 text-container ql-editor">
<%= custom_auto_link(result_text.text, false) %>
<%= custom_auto_link(generate_image_tag_from_token(result_text.text),
simple_format: false,
tags: %w(img)) %>
</div>
</div>
</div>

View file

@ -28,7 +28,9 @@
<div class="row">
<div class="col-xs-12 ql-editor">
<% if strip_tags(step.description).present? %>
<%= custom_auto_link(step.description, false) %>
<%= custom_auto_link(generate_image_tag_from_token(step.description),
simple_format: false,
tags: %w(img)) %>
<% else %>
<em><%=t "projects.reports.elements.step.no_description" %></em>
<% end %>

View file

@ -24,7 +24,9 @@
<li>
<input type="checkbox" disabled="disabled" <%= "checked='checked'" if item.checked %>/>
<span class="<%= 'checked' if item.checked %>">
<%= custom_auto_link(item.text, true, nil, {wrapper_tag: "span"}) %>
<%= custom_auto_link(item.text,
simple_format: true,
wrapper_tag: { wrapper_tag: 'span'}) %>
</span>
</li>
<% end %>

View file

@ -3,7 +3,10 @@
<%= f.text_field :name, style: "margin-top: 10px;" %><br />
<%= f.fields_for :result_text do |ff| %>
<div class="form-group">
<%= ff.tiny_mce_editor(:text, value: @result.result_text.text) %>
<%= ff.tiny_mce_editor(:text,
value: @result.result_text.text,
data: { object_type: 'result_text',
object_id: @result.result_text.id }) %>
</div>
<% end %><br />
<%= f.submit t("result_texts.edit.update"), class: 'btn btn-primary save-result', onclick: "processResult(event, ResultTypeEnum.TEXT, true);" %>

View file

@ -3,7 +3,8 @@
<%= f.text_field :name, style: "margin-top: 10px;" %><br />
<%= f.fields_for :result_text do |ff| %>
<div class="form-group">
<%= ff.tiny_mce_editor(:text) %>
<%= ff.tiny_mce_editor(:text, data: { object_type: 'result_text',
object_id: @result.result_text.id }) %>
</div>
<% end %><br />
<%= f.submit t("result_texts.new.create"), class: 'btn btn-primary save-result', onclick: "processResult(event, ResultTypeEnum.TEXT, false);" %>

View file

@ -1,3 +1,5 @@
<div class="ql-editor">
<%= custom_auto_link(result.result_text.text, false) %>
<%= custom_auto_link(generate_image_tag_from_token(result.result_text.text),
simple_format: false,
tags: %w(img)) %>
</div>

View file

@ -27,7 +27,7 @@
<div class="tab-pane active" role="tabpanel" id="new-step-main">
<%= f.text_field :name, label: t("protocols.steps.new.name"), placeholder: t("protocols.steps.new.name_placeholder") %>
<div class="form-group">
<%= f.tiny_mce_editor(:description) %>
<%= f.tiny_mce_editor(:description, data: { object_type: 'step', object_id: @step.id }) %>
</div>
</div>
<div class="tab-pane" role="tabpanel" id="new-step-checklists">

View file

@ -54,7 +54,9 @@
<em><%= t('protocols.steps.no_description') %></em>
<% else %>
<div class="ql-editor">
<%= custom_auto_link(step.description, false) %>
<%= custom_auto_link(generate_image_tag_from_token(step.description),
simple_format: false,
tags: %w(img)) %>
</div>
<% end %>
</div>

View file

@ -1572,7 +1572,12 @@ en:
expired_description: 'The downloadable file expires in 3 days after its creation.'
# This section contains general words that can be used in any parts of
# application.
tiny_mce:
upload_window_title: 'Insert an image from your computer'
upload_window_label: 'Choose an image'
insert_btn: 'Insert'
error_message: 'You must choose a file'
server_not_respond: "Didn't get a response from the server"
general:
save: "Save"
update: "Update"

View file

@ -322,6 +322,9 @@ Rails.application.routes.draw do
end
end
# tinyMCE image uploader endpoint
post '/tinymce_assets', to: 'tiny_mce_assets#create', as: :tiny_mce_assets
resources :results, only: [:update, :destroy] do
resources :result_comments,
path: '/comments',

View file

@ -0,0 +1,12 @@
class AddAttachmentImageToTinyMceAssets < ActiveRecord::Migration
def change
create_table :tiny_mce_assets do |t|
t.attachment :image
t.integer :estimated_size, default: 0, null: false
t.references :step, index: true
t.references :team, index: true
t.references :result_text, index: true
t.timestamps null: false
end
end
end

View file

@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20170321131116) do
ActiveRecord::Schema.define(version: 20170420075905) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -60,12 +60,13 @@ ActiveRecord::Schema.define(version: 20170321131116) do
t.integer "lock_ttl"
t.integer "version", default: 1
t.boolean "file_processing"
t.integer "team_id"
end
add_index "assets", ["created_at"], name: "index_assets_on_created_at", using: :btree
add_index "assets", ["created_by_id"], name: "index_assets_on_created_by_id", using: :btree
add_index "assets", ["file_file_name"], name: "index_assets_on_file_file_name", using: :gist
add_index "assets", ["last_modified_by_id"], name: "index_assets_on_last_modified_by_id", using: :btree
add_index "assets", ["team_id"], name: "index_assets_on_team_id", using: :btree
create_table "checklist_items", force: :cascade do |t|
t.string "text", null: false
@ -75,7 +76,7 @@ ActiveRecord::Schema.define(version: 20170321131116) do
t.datetime "updated_at", null: false
t.integer "created_by_id"
t.integer "last_modified_by_id"
t.integer "position", default: 0, null: false
t.integer "position"
end
add_index "checklist_items", ["checklist_id"], name: "index_checklist_items_on_checklist_id", using: :btree
@ -93,6 +94,7 @@ ActiveRecord::Schema.define(version: 20170321131116) do
add_index "checklists", ["created_by_id"], name: "index_checklists_on_created_by_id", using: :btree
add_index "checklists", ["last_modified_by_id"], name: "index_checklists_on_last_modified_by_id", using: :btree
add_index "checklists", ["step_id"], name: "index_checklists_on_step_id", using: :btree
create_table "comments", force: :cascade do |t|
t.string "message", null: false
@ -107,6 +109,7 @@ ActiveRecord::Schema.define(version: 20170321131116) do
add_index "comments", ["associated_id"], name: "index_comments_on_associated_id", using: :btree
add_index "comments", ["created_at"], name: "index_comments_on_created_at", using: :btree
add_index "comments", ["last_modified_by_id"], name: "index_comments_on_last_modified_by_id", using: :btree
add_index "comments", ["type"], name: "index_comments_on_type", using: :btree
add_index "comments", ["user_id"], name: "index_comments_on_user_id", using: :btree
create_table "connections", force: :cascade do |t|
@ -170,11 +173,6 @@ ActiveRecord::Schema.define(version: 20170321131116) do
add_index "experiments", ["project_id"], name: "index_experiments_on_project_id", using: :btree
add_index "experiments", ["restored_by_id"], name: "index_experiments_on_restored_by_id", using: :btree
create_table "logs", force: :cascade do |t|
t.integer "team_id", null: false
t.string "message", null: false
end
create_table "my_module_groups", force: :cascade do |t|
t.string "name", null: false
t.datetime "created_at", null: false
@ -185,7 +183,6 @@ ActiveRecord::Schema.define(version: 20170321131116) do
add_index "my_module_groups", ["created_by_id"], name: "index_my_module_groups_on_created_by_id", using: :btree
add_index "my_module_groups", ["experiment_id"], name: "index_my_module_groups_on_experiment_id", using: :btree
add_index "my_module_groups", ["name"], name: "index_my_module_groups_on_name", using: :gist
create_table "my_module_tags", force: :cascade do |t|
t.integer "my_module_id"
@ -225,7 +222,6 @@ ActiveRecord::Schema.define(version: 20170321131116) do
add_index "my_modules", ["experiment_id"], name: "index_my_modules_on_experiment_id", using: :btree
add_index "my_modules", ["last_modified_by_id"], name: "index_my_modules_on_last_modified_by_id", using: :btree
add_index "my_modules", ["my_module_group_id"], name: "index_my_modules_on_my_module_group_id", using: :btree
add_index "my_modules", ["name"], name: "index_my_modules_on_name", using: :gist
add_index "my_modules", ["restored_by_id"], name: "index_my_modules_on_restored_by_id", using: :btree
create_table "notifications", force: :cascade do |t|
@ -258,7 +254,6 @@ ActiveRecord::Schema.define(version: 20170321131116) do
add_index "projects", ["archived_by_id"], name: "index_projects_on_archived_by_id", using: :btree
add_index "projects", ["created_by_id"], name: "index_projects_on_created_by_id", using: :btree
add_index "projects", ["last_modified_by_id"], name: "index_projects_on_last_modified_by_id", using: :btree
add_index "projects", ["name"], name: "index_projects_on_name", using: :gist
add_index "projects", ["restored_by_id"], name: "index_projects_on_restored_by_id", using: :btree
add_index "projects", ["team_id"], name: "index_projects_on_team_id", using: :btree
@ -270,7 +265,6 @@ ActiveRecord::Schema.define(version: 20170321131116) do
t.integer "team_id", null: false
end
add_index "protocol_keywords", ["name"], name: "index_protocol_keywords_on_name", using: :btree
add_index "protocol_keywords", ["team_id"], name: "index_protocol_keywords_on_team_id", using: :btree
create_table "protocol_protocol_keywords", force: :cascade do |t|
@ -303,11 +297,9 @@ ActiveRecord::Schema.define(version: 20170321131116) do
add_index "protocols", ["added_by_id"], name: "index_protocols_on_added_by_id", using: :btree
add_index "protocols", ["archived_by_id"], name: "index_protocols_on_archived_by_id", using: :btree
add_index "protocols", ["authors"], name: "index_protocols_on_authors", using: :btree
add_index "protocols", ["description"], name: "index_protocols_on_description", using: :btree
add_index "protocols", ["my_module_id"], name: "index_protocols_on_my_module_id", using: :btree
add_index "protocols", ["name"], name: "index_protocols_on_name", using: :btree
add_index "protocols", ["parent_id"], name: "index_protocols_on_parent_id", using: :btree
add_index "protocols", ["protocol_type"], name: "index_protocols_on_protocol_type", using: :btree
add_index "protocols", ["restored_by_id"], name: "index_protocols_on_restored_by_id", using: :btree
add_index "protocols", ["team_id"], name: "index_protocols_on_team_id", using: :btree
@ -393,7 +385,6 @@ ActiveRecord::Schema.define(version: 20170321131116) do
add_index "results", ["created_at"], name: "index_results_on_created_at", using: :btree
add_index "results", ["last_modified_by_id"], name: "index_results_on_last_modified_by_id", using: :btree
add_index "results", ["my_module_id"], name: "index_results_on_my_module_id", using: :btree
add_index "results", ["name"], name: "index_results_on_name", using: :gist
add_index "results", ["restored_by_id"], name: "index_results_on_restored_by_id", using: :btree
add_index "results", ["user_id"], name: "index_results_on_user_id", using: :btree
@ -458,7 +449,6 @@ ActiveRecord::Schema.define(version: 20170321131116) do
end
add_index "samples", ["last_modified_by_id"], name: "index_samples_on_last_modified_by_id", using: :btree
add_index "samples", ["name"], name: "index_samples_on_name", using: :gist
add_index "samples", ["sample_group_id"], name: "index_samples_on_sample_group_id", using: :btree
add_index "samples", ["sample_type_id"], name: "index_samples_on_sample_type_id", using: :btree
add_index "samples", ["team_id"], name: "index_samples_on_team_id", using: :btree
@ -511,7 +501,6 @@ ActiveRecord::Schema.define(version: 20170321131116) do
add_index "steps", ["created_at"], name: "index_steps_on_created_at", using: :btree
add_index "steps", ["last_modified_by_id"], name: "index_steps_on_last_modified_by_id", using: :btree
add_index "steps", ["name"], name: "index_steps_on_name", using: :gist
add_index "steps", ["position"], name: "index_steps_on_position", using: :btree
add_index "steps", ["protocol_id"], name: "index_steps_on_protocol_id", using: :btree
add_index "steps", ["user_id"], name: "index_steps_on_user_id", using: :btree
@ -524,12 +513,14 @@ ActiveRecord::Schema.define(version: 20170321131116) do
t.integer "last_modified_by_id"
t.tsvector "data_vector"
t.string "name", default: ""
t.integer "team_id"
end
add_index "tables", ["created_at"], name: "index_tables_on_created_at", using: :btree
add_index "tables", ["created_by_id"], name: "index_tables_on_created_by_id", using: :btree
add_index "tables", ["data_vector"], name: "index_tables_on_data_vector", using: :gin
add_index "tables", ["last_modified_by_id"], name: "index_tables_on_last_modified_by_id", using: :btree
add_index "tables", ["team_id"], name: "index_tables_on_team_id", using: :btree
create_table "tags", force: :cascade do |t|
t.string "name", null: false
@ -543,7 +534,6 @@ ActiveRecord::Schema.define(version: 20170321131116) do
add_index "tags", ["created_by_id"], name: "index_tags_on_created_by_id", using: :btree
add_index "tags", ["last_modified_by_id"], name: "index_tags_on_last_modified_by_id", using: :btree
add_index "tags", ["name"], name: "index_tags_on_name", using: :gist
add_index "tags", ["project_id"], name: "index_tags_on_project_id", using: :btree
create_table "teams", force: :cascade do |t|
@ -570,6 +560,23 @@ ActiveRecord::Schema.define(version: 20170321131116) do
t.datetime "file_updated_at"
end
create_table "tiny_mce_assets", force: :cascade do |t|
t.string "image_file_name"
t.string "image_content_type"
t.integer "image_file_size"
t.datetime "image_updated_at"
t.integer "estimated_size", default: 0, null: false
t.integer "step_id"
t.integer "team_id"
t.integer "result_text_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "tiny_mce_assets", ["result_text_id"], name: "index_tiny_mce_assets_on_result_text_id", using: :btree
add_index "tiny_mce_assets", ["step_id"], name: "index_tiny_mce_assets_on_step_id", using: :btree
add_index "tiny_mce_assets", ["team_id"], name: "index_tiny_mce_assets_on_team_id", using: :btree
create_table "tokens", force: :cascade do |t|
t.string "token", null: false
t.integer "ttl", null: false
@ -601,11 +608,11 @@ ActiveRecord::Schema.define(version: 20170321131116) do
add_index "user_notifications", ["user_id"], name: "index_user_notifications_on_user_id", using: :btree
create_table "user_projects", force: :cascade do |t|
t.integer "role", default: 0
t.integer "user_id", null: false
t.integer "project_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "role"
t.integer "user_id", null: false
t.integer "project_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "assigned_by_id"
end
@ -671,7 +678,6 @@ ActiveRecord::Schema.define(version: 20170321131116) do
add_index "users", ["authentication_token"], name: "index_users_on_authentication_token", unique: true, using: :btree
add_index "users", ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true, using: :btree
add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree
add_index "users", ["full_name"], name: "index_users_on_full_name", using: :btree
add_index "users", ["invitation_token"], name: "index_users_on_invitation_token", unique: true, using: :btree
add_index "users", ["invitations_count"], name: "index_users_on_invitations_count", using: :btree
add_index "users", ["invited_by_id"], name: "index_users_on_invited_by_id", using: :btree
@ -735,7 +741,6 @@ ActiveRecord::Schema.define(version: 20170321131116) do
add_foreign_key "experiments", "users", column: "created_by_id"
add_foreign_key "experiments", "users", column: "last_modified_by_id"
add_foreign_key "experiments", "users", column: "restored_by_id"
add_foreign_key "logs", "teams"
add_foreign_key "my_module_groups", "experiments"
add_foreign_key "my_module_groups", "users", column: "created_by_id"
add_foreign_key "my_module_tags", "users", column: "created_by_id"

View file

@ -0,0 +1,9 @@
namespace :tiny_mce_asset do
desc 'Remove obsolete images that were created on new steps or '\
'results and the step/result didn\'t get saved.'
task remove_obsolete_images: :environment do
TinyMceAsset.where('step_id IS ? AND ' \
'result_text_id IS ? AND created_at < ?',
nil, nil, 7.days.ago)
end
end