diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 59dc86689..cb7c7b3ac 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -117,68 +117,7 @@ module ApplicationHelper
# Check if text have smart annotations of resources
# and outputs a link to resource
def smart_annotation_filter_resources(text)
- sa_reg = /\[\#(.*?)~(prj|exp|tsk|sam)~([0-9a-zA-Z]+)\]/
- new_text = text.gsub(sa_reg) do |el|
- match = el.match(sa_reg)
- case match[2]
- when 'prj'
- project = Project.find_by_id(match[3].base62_decode)
- next unless project
- if project.archived?
- "" \
- "#{sanitize_input(match[2])}" \
- "#{link_to project.name,
- projects_archive_path} #{I18n.t('atwho.res.archived')}"
- else
- "" \
- "#{sanitize_input(match[2])}" \
- "#{link_to project.name,
- project_path(project)}"
- end
- when 'exp'
- experiment = Experiment.find_by_id(match[3].base62_decode)
- next unless experiment
- if experiment.archived?
- "" \
- "#{sanitize_input(match[2])}" \
- "#{link_to experiment.name,
- experiment_archive_project_path(experiment.project)} " \
- "#{I18n.t('atwho.res.archived')}"
- else
- ""\
- "#{sanitize_input(match[2])}" \
- "#{link_to experiment.name,
- canvas_experiment_path(experiment)}"
- end
- when 'tsk'
- my_module = MyModule.find_by_id(match[3].base62_decode)
- next unless my_module
- if my_module.archived?
- "" \
- "#{sanitize_input(match[2])}" \
- "#{link_to my_module.name,
- module_archive_experiment_path(my_module.experiment)} " \
- "#{I18n.t('atwho.res.archived')}"
- else
- "" \
- "#{sanitize_input(match[2])}" \
- "#{link_to my_module.name,
- protocols_my_module_path(my_module)}"
- end
- when 'sam'
- sample = Sample.find_by_id(match[3].base62_decode)
- if sample
- "" \
- "#{link_to(sample.name,
- sample_path(sample.id),
- class: 'sample-info-link')}"
- else
- "" \
- "#{match[1]} #{I18n.t('atwho.res.deleted')}"
- end
- end
- end
- new_text
+ SmartAnnotations::TagToHtml.new(current_user, text).html
end
# Check if text have smart annotations of users
diff --git a/app/permissions/project.rb b/app/permissions/project.rb
index 3b07cf6cd..e3701db6d 100644
--- a/app/permissions/project.rb
+++ b/app/permissions/project.rb
@@ -16,7 +16,6 @@ Canaid::Permissions.register_for(Project) do
# project: read, read activities, read comments, read users, read archive,
# read notifications
# reports: read
- # samples: read
can :read_project do |user, project|
user.is_member_of_project?(project) ||
user.is_admin_of_team?(project.team) ||
diff --git a/app/services/smart_annotations/permision_eval.rb b/app/services/smart_annotations/permision_eval.rb
new file mode 100644
index 000000000..87a14c443
--- /dev/null
+++ b/app/services/smart_annotations/permision_eval.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+module SmartAnnotations
+ class PermissionEval
+ class << self
+ include Canaid::Helpers::PermissionsHelper
+
+ def check(user, type, object)
+ send("validate_#{type}_permissions", user, object)
+ end
+
+ private
+
+ def validate_prj_permissions(user, object)
+ can_read_project?(user, object)
+ end
+
+ def validate_exp_permissions(user, object)
+ can_read_experiment?(user, object)
+ end
+
+ def validate_tsk_permissions(user, object)
+ can_read_experiment?(user, object.experiment)
+ end
+
+ def validate_sam_permissions(user, object)
+ can_read_team?(user, object.team)
+ end
+
+ def validate_rep_item_permissions(user, object)
+ can_read_team?(user, object.repository.team)
+ end
+ end
+ end
+end
diff --git a/app/services/smart_annotations/preview.rb b/app/services/smart_annotations/preview.rb
new file mode 100644
index 000000000..108e4af52
--- /dev/null
+++ b/app/services/smart_annotations/preview.rb
@@ -0,0 +1,73 @@
+# frozen_string_literal: true
+
+module SmartAnnotations
+ class Preview
+ class << self
+ def html(name, type, object)
+ send("generate_#{type}_snippet", name, object)
+ end
+
+ private
+
+ ROUTES = Rails.application.routes.url_helpers
+
+ def generate_prj_snippet(_, object)
+ if object.archived?
+ return "PRJ#{object.name}" \
+ "#{I18n.t('atwho.res.archived')}"
+ end
+ "PRJ" \
+ "#{object.name}"
+ end
+
+ def generate_exp_snippet(_, object)
+ if object.archived?
+ return "EXP" \
+ "#{object.name} #{I18n.t('atwho.res.archived')}"
+ end
+ "EXP" \
+ "#{object.name}"
+ end
+
+ def generate_tsk_snippet(_, object)
+ if object.archived?
+ return "TSK#{object.name} #{I18n.t('atwho.res.archived')}"
+ end
+ "TSK" \
+ "" \
+ "#{object.name}"
+ end
+
+ def generate_sam_snippet(name, object)
+ if object
+ return "" \
+ "#{object.name}"
+ end
+ "" \
+ "#{name} #{I18n.t('atwho.res.deleted')}"
+ end
+
+ def generate_rep_item_snippet(name, object)
+ if object
+ repository_name = object.repository.name
+ return "" \
+ "#{trim_repository_name(repository_name)}" \
+ "#{object.name}"
+ end
+ "REP" \
+ "#{name} #{I18n.t('atwho.res.deleted')}"
+ end
+
+ def trim_repository_name(name)
+ name.strip.slice(0..2).upcase
+ end
+ end
+ end
+end
diff --git a/app/services/smart_annotations/tag_to_html.rb b/app/services/smart_annotations/tag_to_html.rb
new file mode 100644
index 000000000..69616a6f8
--- /dev/null
+++ b/app/services/smart_annotations/tag_to_html.rb
@@ -0,0 +1,67 @@
+# frozen_string_literal: true
+
+require 'smart_annotations/permision_eval'
+require 'smart_annotations/preview'
+
+module SmartAnnotations
+ class TagToHtml
+ attr_reader :html
+
+ def initialize(user, text)
+ parse(user, text)
+ end
+
+ private
+
+ REGEX = /\[\#(.*?)~(prj|exp|tsk|sam|rep_item)~([0-9a-zA-Z]+)\]/
+ OBJECT_MAPPINGS = { prj: Project,
+ exp: Experiment,
+ tsk: MyModule,
+ sam: Sample,
+ rep_item: RepositoryRow }.freeze
+
+ def parse(user, text)
+ @html = text.gsub(REGEX) do |el|
+ value = extract_values(el)
+ type = value[:object_type]
+ begin
+ object = fetch_object(type, value[:object_id])
+ # handle samples/repository_items edge case
+ if type.in? %w(sam rep_item)
+ sample_or_repository_item(value[:name], user, type, object)
+ else
+ next unless object && SmartAnnotations::PermissionEval.check(user,
+ type,
+ object)
+ SmartAnnotations::Preview.html(nil, type, object)
+ end
+ rescue ActiveRecord::RecordNotFound
+ next
+ end
+ end
+ end
+
+ def sample_or_repository_item(name, user, type, object)
+ if object && SmartAnnotations::PermissionEval.check(user, type, object)
+ return SmartAnnotations::Preview.html(nil, type, object)
+ end
+ SmartAnnotations::Preview.html(name, type, object)
+ end
+
+ def extract_values(element)
+ match = element.match(REGEX)
+ {
+ name: match[1],
+ object_type: match[2],
+ object_id: match[3].base62_decode
+ }
+ end
+
+ def fetch_object(type, id)
+ klass = OBJECT_MAPPINGS.fetch(type.to_sym) do
+ raise ActiveRecord::RecordNotFound.new("#{type} does not exist")
+ end
+ klass.find_by_id(id)
+ end
+ end
+end
diff --git a/spec/services/smart_annotations/permission_eval_spec.rb b/spec/services/smart_annotations/permission_eval_spec.rb
new file mode 100644
index 000000000..0add140ba
--- /dev/null
+++ b/spec/services/smart_annotations/permission_eval_spec.rb
@@ -0,0 +1,47 @@
+require 'rails_helper'
+require 'smart_annotations/permision_eval'
+
+describe SmartAnnotations::PermissionEval do
+ let(:subject) { described_class }
+ let(:user) { create :user }
+ let(:team) { create :team }
+ let(:user_team) { create :user_team, user: user, team: team, role: 2 }
+ let(:project) { create :project, name: 'my project' }
+ let(:experiment) do
+ create :experiment, name: 'my experiment',
+ project: project,
+ created_by: user,
+ last_modified_by: user
+ end
+ let(:task) { create :my_module, name: 'task', experiment: experiment }
+ let(:repository) { create :repository, team: team, created_by: user }
+ let(:repository_item) { create :repository_row, repository: repository }
+
+ describe '#validate_prj_permissions/2' do
+ it 'returns a boolean' do
+ value = subject.send(:validate_prj_permissions, user, project)
+ expect(value).to be_in([true, false])
+ end
+ end
+
+ describe '#validate_exp_permissions/2' do
+ it 'returns a boolean' do
+ value = subject.send(:validate_exp_permissions, user, experiment)
+ expect(value).to be_in([true, false])
+ end
+ end
+
+ describe '#validate_tsk_permissions/2' do
+ it 'returns a boolean' do
+ value = subject.send(:validate_tsk_permissions, user, task)
+ expect(value).to be_in([true, false])
+ end
+ end
+
+ describe '#validate_rep_item_permissions/2' do
+ it 'returns a boolean' do
+ value = subject.send(:validate_rep_item_permissions, user, repository_item)
+ expect(value).to be_in([true, false])
+ end
+ end
+end
diff --git a/spec/services/smart_annotations/preview_spec.rb b/spec/services/smart_annotations/preview_spec.rb
new file mode 100644
index 000000000..8ce623cd5
--- /dev/null
+++ b/spec/services/smart_annotations/preview_spec.rb
@@ -0,0 +1,63 @@
+require 'rails_helper'
+require 'smart_annotations/preview'
+
+describe SmartAnnotations::Preview do
+ let(:subject) { described_class }
+ let(:user) { create :user }
+ let(:project) { create :project, name: 'my project' }
+ let(:experiment) do
+ create :experiment, name: 'my experiment',
+ project: project,
+ created_by: user,
+ last_modified_by: user
+ end
+ let(:task) { create :my_module, name: 'task', experiment: experiment }
+
+ describe 'Project annotations with type prj' do
+ it 'returns a html snippet' do
+ snippet = subject.html(nil, 'prj', project)
+ expect(snippet).to eq(
+ "PRJ" \
+ "my project"
+ )
+ end
+ end
+
+ context 'Experiment annotations with type exp' do
+ it 'returns a html snippet' do
+ snippet = subject.html(nil, 'exp', experiment)
+ expect(snippet).to eq(
+ "EXP" \
+ "my experiment"
+ )
+ end
+ end
+
+ context 'MyModule annotations with type tsk' do
+ it 'returns a html snippet' do
+ snippet = subject.html(nil, 'tsk', task)
+ expect(snippet).to eq(
+ "TSK" \
+ "task"
+ )
+ end
+ end
+
+ context 'Repository item annotations with type rep_item' do
+ it 'returns a html snippet' do
+ snippet = subject.html('my item', 'rep_item', nil)
+ expect(snippet).to eq(
+ 'REPmy item (deleted)'
+ )
+ end
+ end
+
+ describe '#trim_repository_name/1' do
+ it 'is returns a 3 letter upcase string' do
+ trimmed_repository_name = subject.__send__(
+ :trim_repository_name, 'banana'
+ )
+ expect(trimmed_repository_name).to eq('BAN')
+ end
+ end
+end
diff --git a/spec/services/smart_annotations/tag_to_html_spec.rb b/spec/services/smart_annotations/tag_to_html_spec.rb
new file mode 100644
index 000000000..a15f3f25e
--- /dev/null
+++ b/spec/services/smart_annotations/tag_to_html_spec.rb
@@ -0,0 +1,44 @@
+require 'rails_helper'
+
+describe SmartAnnotations::TagToHtml do
+ let!(:user) { create :user }
+ let!(:team) { create :team }
+ let!(:user_team) { create :user_team, user: user, team: team, role: 2 }
+ let!(:project) { create :project, name: 'my project', team: team }
+ let!(:user_project) do
+ create :user_project, project: project, user: user, role: 0
+ end
+ let(:text) do
+ "My annotation of [#my project~prj~#{project.id.base62_encode}]"
+ end
+ let(:subject) { described_class.new(user, text) }
+ describe 'Parsed text' do
+ it 'returns a existing string with smart annotation' do
+ expect(subject.html).to eq(
+ "My annotation of PRJ"\
+ "my project"
+ )
+ end
+ end
+
+ describe '#extract_values/1' do
+ it 'returns a parsed hash of smart annotation' do
+ values = subject.send(:extract_values, '[#my project~prj~1]')
+ expect(values[:name]).to eq 'my project'
+ expect(values[:object_id]).to eq 1
+ expect(values[:object_type]).to eq 'prj'
+ end
+ end
+
+ describe '#fetch_object/2' do
+ it 'rises an error if type is not valid' do
+ expect {
+ subject.send(:fetch_object, 'banana', project.id)
+ }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+
+ it 'returns the required object' do
+ expect(subject.send(:fetch_object, 'prj', project.id)).to eq project
+ end
+ end
+end