From 659a088058437821f57b94208aab918ac33a5dd9 Mon Sep 17 00:00:00 2001 From: zmagod Date: Thu, 29 Mar 2018 15:55:26 +0200 Subject: [PATCH 1/2] refactor smart annotation parser, adds repository item to smart annotation parser [fixes SCI-2222] --- app/helpers/application_helper.rb | 63 +-------------- app/permissions/project.rb | 1 - .../smart_annotations/permision_eval.rb | 35 +++++++++ app/services/smart_annotations/preview.rb | 77 +++++++++++++++++++ app/services/smart_annotations/tag_to_html.rb | 66 ++++++++++++++++ .../smart_annotations/permission_eval_spec.rb | 47 +++++++++++ .../smart_annotations/preview_spec.rb | 63 +++++++++++++++ .../smart_annotations/tag_to_html_spec.rb | 44 +++++++++++ 8 files changed, 333 insertions(+), 63 deletions(-) create mode 100644 app/services/smart_annotations/permision_eval.rb create mode 100644 app/services/smart_annotations/preview.rb create mode 100644 app/services/smart_annotations/tag_to_html.rb create mode 100644 spec/services/smart_annotations/permission_eval_spec.rb create mode 100644 spec/services/smart_annotations/preview_spec.rb create mode 100644 spec/services/smart_annotations/tag_to_html_spec.rb 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..e6be1a24b --- /dev/null +++ b/app/services/smart_annotations/preview.rb @@ -0,0 +1,77 @@ +# 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..522271404 --- /dev/null +++ b/app/services/smart_annotations/tag_to_html.rb @@ -0,0 +1,66 @@ +# 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 From 01a548e8b3a0b9176776a1ea17ec228689dd3a97 Mon Sep 17 00:00:00 2001 From: zmagod Date: Thu, 29 Mar 2018 16:11:21 +0200 Subject: [PATCH 2/2] fix hound --- app/services/smart_annotations/preview.rb | 20 ++++++++----------- app/services/smart_annotations/tag_to_html.rb | 1 + 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/app/services/smart_annotations/preview.rb b/app/services/smart_annotations/preview.rb index e6be1a24b..108e4af52 100644 --- a/app/services/smart_annotations/preview.rb +++ b/app/services/smart_annotations/preview.rb @@ -23,11 +23,9 @@ module SmartAnnotations def generate_exp_snippet(_, object) if object.archived? - return "EXP" \ - "#{object.name}" \ - "#{I18n.t('atwho.res.archived')}" + return "EXP" \ + "#{object.name} #{I18n.t('atwho.res.archived')}" end "EXP" \ "#{object.name}" @@ -35,11 +33,10 @@ module SmartAnnotations def generate_tsk_snippet(_, object) if object.archived? - return "TSK" \ - "#{object.name}" \ - "#{I18n.t('atwho.res.archived')}" + return "TSK#{object.name} #{I18n.t('atwho.res.archived')}" end "TSK" \ "" \ @@ -62,8 +59,7 @@ module SmartAnnotations return "" \ "#{trim_repository_name(repository_name)}" \ "" \ - "#{object.name}" + "class='record-info-link'>#{object.name}" end "REP" \ "#{name} #{I18n.t('atwho.res.deleted')}" diff --git a/app/services/smart_annotations/tag_to_html.rb b/app/services/smart_annotations/tag_to_html.rb index 522271404..69616a6f8 100644 --- a/app/services/smart_annotations/tag_to_html.rb +++ b/app/services/smart_annotations/tag_to_html.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require 'smart_annotations/permision_eval' require 'smart_annotations/preview'