lcms-engine 0.3.0 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.codeclimate.yml +1 -1
- data/.env.docker +1 -1
- data/.rubocop.yml +8 -7
- data/.ruby-version +1 -1
- data/CHANGELOG.md +16 -1
- data/Dockerfile +1 -1
- data/Gemfile.lock +223 -184
- data/README.md +3 -2
- data/app/controllers/concerns/lcms/engine/reimportable.rb +11 -1
- data/app/controllers/lcms/engine/admin/admin_controller.rb +1 -1
- data/app/controllers/lcms/engine/admin/documents_controller.rb +19 -4
- data/app/controllers/lcms/engine/admin/materials_controller.rb +12 -3
- data/app/controllers/lcms/engine/admin/standards_controller.rb +6 -1
- data/app/controllers/lcms/engine/admin/users_controller.rb +6 -1
- data/app/entities/lcms/engine/grades.rb +17 -6
- data/app/entities/lcms/engine/media_embed.rb +1 -1
- data/app/entities/lcms/engine/roman_numerals.rb +1 -1
- data/app/forms/lcms/engine/document_form.rb +19 -14
- data/app/forms/lcms/engine/material_form.rb +3 -2
- data/app/helpers/admin/components_helper.rb +2 -2
- data/app/helpers/lcms/engine/view_helper.rb +1 -1
- data/app/interactors/lcms/engine/explore_curriculum_interactor.rb +1 -1
- data/app/javascript/components/admin/ImportStatus.jsx +7 -3
- data/app/javascript/components/admin/MultiSelectedOperation.jsx +1 -0
- data/app/javascript/components/admin/association-picker/AssociationPicker.jsx +1 -0
- data/app/javascript/components/admin/association-picker/AssociationPickerResults.jsx +1 -0
- data/app/javascript/components/admin/association-picker/AssociationPickerWindow.jsx +1 -0
- data/app/javascript/components/admin/curriculum/CurriculumEditor.jsx +1 -0
- data/app/javascript/components/admin/resource-picker/ResourcePicker.jsx +1 -0
- data/app/jobs/concerns/lcms/engine/nested_resque_job.rb +1 -4
- data/app/jobs/lcms/engine/document_generate_gdoc_job.rb +2 -2
- data/app/jobs/lcms/engine/document_generate_job.rb +1 -1
- data/app/jobs/lcms/engine/document_parse_job.rb +1 -1
- data/app/models/lcms/engine/component.rb +3 -3
- data/app/models/lcms/engine/download.rb +1 -1
- data/app/models/lcms/engine/search/document.rb +4 -3
- data/app/presenters/lcms/engine/document_presenter.rb +2 -2
- data/app/queries/lcms/engine/admin_documents_query.rb +1 -1
- data/app/serializers/lcms/engine/previews_material_serializer.rb +1 -0
- data/app/services/lcms/engine/document_build_service.rb +4 -0
- data/app/services/lcms/engine/html_sanitizer.rb +5 -5
- data/app/services/lcms/engine/lessons_gdoc_bundler.rb +1 -1
- data/app/services/lcms/engine/material_build_service.rb +6 -2
- data/app/services/lcms/engine/material_preview_generator.rb +2 -2
- data/app/services/lcms/engine/react_materials_resolver.rb +3 -2
- data/app/views/lcms/engine/admin/batch_reimports/_search_form.html.erb +1 -1
- data/app/views/lcms/engine/admin/curriculums/edit.html.erb +1 -3
- data/app/views/lcms/engine/admin/documents/_materials_links.html.erb +2 -2
- data/app/views/lcms/engine/admin/documents/_search_form.html.erb +1 -1
- data/app/views/lcms/engine/admin/documents/index.html.erb +2 -2
- data/app/views/lcms/engine/admin/documents/new.html.erb +1 -1
- data/app/views/lcms/engine/admin/materials/_search_form.html.erb +1 -1
- data/app/views/lcms/engine/admin/materials/index.html.erb +4 -4
- data/app/views/lcms/engine/admin/resource_bulk_edits/new.html.erb +1 -1
- data/app/views/lcms/engine/admin/resources/_fields.html.erb +3 -0
- data/app/views/lcms/engine/admin/resources/_search_form.html.erb +1 -1
- data/app/views/lcms/engine/documents/gdoc/_agenda.html.erb +1 -1
- data/app/views/lcms/engine/documents/show.html.erb +1 -1
- data/app/views/lcms/engine/materials/show.html.erb +1 -1
- data/app/views/lcms/engine/resources/_download.html.erb +2 -2
- data/app/views/lcms/engine/resources/_unit_bundles.html.erb +2 -2
- data/config/locales/admin/en.yml +1 -1
- data/db/schema.rb +1 -1
- data/lcms-engine.gemspec +12 -10
- data/lib/doc_template.rb +1 -1
- data/lib/doc_template/objects/agenda_metadata.rb +1 -1
- data/lib/doc_template/objects/metadata_helpers.rb +1 -1
- data/lib/doc_template/objects/toc_metadata.rb +2 -2
- data/lib/doc_template/tables/base.rb +3 -2
- data/lib/doc_template/tags/activity_metadata_type_tag.rb +1 -1
- data/lib/doc_template/tags/answer_space_tag.rb +1 -1
- data/lib/doc_template/tags/base_tag.rb +3 -3
- data/lib/doc_template/tags/columns_tag.rb +1 -1
- data/lib/doc_template/tags/def_tag.rb +1 -1
- data/lib/doc_template/tags/expand_tag.rb +1 -0
- data/lib/doc_template/tags/heading_tag.rb +1 -1
- data/lib/doc_template/tags/inset_tag.rb +2 -2
- data/lib/doc_template/tags/latex_tag.rb +1 -1
- data/lib/doc_template/tags/page_break_tag.rb +1 -1
- data/lib/doc_template/tags/pd_tag.rb +4 -4
- data/lib/doc_template/tags/section_tag.rb +2 -2
- data/lib/doc_template/tags/standard_tag.rb +3 -3
- data/lib/doc_template/tags/table_preserve_alignment_tag.rb +1 -1
- data/lib/document_exporter/gdoc/base.rb +1 -1
- data/lib/document_renderer/part.rb +3 -3
- data/lib/elasticsearch/persistence/repository/response/results.rb +1 -1
- data/lib/lcms/engine/engine.rb +1 -1
- data/lib/lcms/engine/version.rb +1 -1
- data/lib/lt/lcms/metadata/base_service.rb +2 -1
- data/lib/lt/lcms/metadata/context.rb +2 -2
- data/lib/lt/lcms/metadata/service.rb +3 -1
- data/lib/resque_job.rb +3 -6
- data/lib/standard_importer.rb +4 -4
- data/lib/tasks/cloud66.rake +6 -4
- data/lib/tasks/document.rake +3 -3
- data/lib/tasks/google.rake +1 -1
- data/package.json +1 -0
- data/spec/controllers/admin/association_picker_controller_spec.rb +6 -8
- data/spec/controllers/admin/documents_controller_spec.rb +3 -1
- data/spec/controllers/admin/materials_controller_spec.rb +1 -1
- data/spec/controllers/admin/resources_controller_spec.rb +2 -2
- data/spec/dummy/.env.docker +5 -0
- data/spec/dummy/config/environments/production.rb +1 -1
- data/spec/dummy/config/puma.rb +3 -3
- data/spec/entities/grades_spec.rb +12 -0
- data/spec/features/admin/lessons/add_lesson_spec.rb +3 -3
- data/spec/features/admin/materials/add_material_spec.rb +3 -3
- data/spec/forms/document_form_spec.rb +11 -1
- data/spec/lib/doc_template/tables/shared_examples/remove_table.rb +1 -1
- data/spec/rails_helper.rb +19 -2
- data/spec/services/document_build_service_spec.rb +1 -1
- data/yarn.lock +145 -170
- metadata +81 -34
data/README.md
CHANGED
@@ -13,8 +13,9 @@ separately, simplifying the client applications in the process.
|
|
13
13
|
|
14
14
|
|Branch|Rails version|
|
15
15
|
|------|-------------|
|
16
|
-
|master|Rails
|
17
|
-
|0.
|
16
|
+
|master|Rails 6|
|
17
|
+
|0.3.x|Rails 5.2.4.5|
|
18
|
+
|0.1.x|Rails 4.2.11.3|
|
18
19
|
|
19
20
|
This is still a [work in progress](https://github.com/learningtapestry/lcms-engine/issues/3). The
|
20
21
|
initial phase of the project consisted in extracting as much code as possible from the client
|
@@ -19,10 +19,20 @@ module Lcms
|
|
19
19
|
status = job_class.status(jid)
|
20
20
|
obj[jid] = {
|
21
21
|
status: status,
|
22
|
-
result: (status == :done ? job_class
|
22
|
+
result: (status == :done ? prepare_result(job_class, jid) : nil)
|
23
23
|
}.compact
|
24
24
|
end
|
25
25
|
end
|
26
|
+
|
27
|
+
def prepare_result(job_class, jid)
|
28
|
+
jid_res = job_class.fetch_result(jid)
|
29
|
+
return jid_res if jid_res['ok']
|
30
|
+
|
31
|
+
{
|
32
|
+
ok: false,
|
33
|
+
errors: Array.wrap("<a href=\"#{jid_res['link']}\">Source</a>: #{jid_res['errors'].join(', ')}")
|
34
|
+
}
|
35
|
+
end
|
26
36
|
end
|
27
37
|
end
|
28
38
|
end
|
@@ -10,9 +10,10 @@ module Lcms
|
|
10
10
|
include Reimportable
|
11
11
|
|
12
12
|
before_action :find_selected, only: %i(destroy_selected reimport_selected)
|
13
|
+
before_action :set_query_params
|
13
14
|
|
14
15
|
def index
|
15
|
-
@query = OpenStruct.new
|
16
|
+
@query = OpenStruct.new @query_params
|
16
17
|
@documents = DocTemplate.config['queries']['document'].constantize.call(@query, page: params[:page])
|
17
18
|
render_customized_view
|
18
19
|
end
|
@@ -23,7 +24,7 @@ module Lcms
|
|
23
24
|
@document = reimport_lesson
|
24
25
|
if @document.save
|
25
26
|
redirect_to AdminController.document_path(@document.document),
|
26
|
-
notice: t('.success', name: @document.document.name)
|
27
|
+
notice: t('.success', name: @document.document.name, errors: collect_errors)
|
27
28
|
else
|
28
29
|
render :new
|
29
30
|
end
|
@@ -32,12 +33,12 @@ module Lcms
|
|
32
33
|
def destroy
|
33
34
|
@document = Document.find(params[:id])
|
34
35
|
@document.destroy
|
35
|
-
redirect_to admin_documents_path(query:
|
36
|
+
redirect_to admin_documents_path(query: @query_params), notice: t('.success')
|
36
37
|
end
|
37
38
|
|
38
39
|
def destroy_selected
|
39
40
|
count = @documents.destroy_all.count
|
40
|
-
redirect_to admin_documents_path(query:
|
41
|
+
redirect_to admin_documents_path(query: @query_params), notice: t('.success', count: count)
|
41
42
|
end
|
42
43
|
|
43
44
|
def import_status
|
@@ -67,6 +68,12 @@ module Lcms
|
|
67
68
|
@props = { jobs: jobs, type: :documents, links: view_links }
|
68
69
|
end
|
69
70
|
|
71
|
+
def collect_errors
|
72
|
+
return if @document.service_errors.empty?
|
73
|
+
|
74
|
+
"Errors: #{@document.service_errors.join(' ')}"
|
75
|
+
end
|
76
|
+
|
70
77
|
def find_selected
|
71
78
|
return head(:bad_request) unless params[:selected_ids].present?
|
72
79
|
|
@@ -105,6 +112,14 @@ module Lcms
|
|
105
112
|
MaterialForm.new({ link: material.file_url, source_type: material.source_type }, google_credentials).save
|
106
113
|
end
|
107
114
|
end
|
115
|
+
|
116
|
+
def set_query_params
|
117
|
+
@query_params = params[:query]
|
118
|
+
&.permit(
|
119
|
+
:broken_materials, :course, :grade, :inactive, :locale, :module, :only_failed, :reimport_required,
|
120
|
+
:search_term, :sort_by
|
121
|
+
) || {}
|
122
|
+
end
|
108
123
|
end
|
109
124
|
end
|
110
125
|
end
|
@@ -8,9 +8,10 @@ module Lcms
|
|
8
8
|
include Reimportable
|
9
9
|
|
10
10
|
before_action :find_selected, only: %i(destroy_selected reimport_selected)
|
11
|
+
before_action :set_query_params
|
11
12
|
|
12
13
|
def index
|
13
|
-
@query = OpenStruct.new
|
14
|
+
@query = OpenStruct.new @query_params
|
14
15
|
@materials = DocTemplate.config['queries']['material'].constantize.call(@query, page: params[:page])
|
15
16
|
render_customized_view
|
16
17
|
end
|
@@ -32,12 +33,12 @@ module Lcms
|
|
32
33
|
def destroy
|
33
34
|
material = Material.find(params[:id])
|
34
35
|
material.destroy
|
35
|
-
redirect_to admin_materials_path(query:
|
36
|
+
redirect_to admin_materials_path(query: @query_params), notice: t('.success')
|
36
37
|
end
|
37
38
|
|
38
39
|
def destroy_selected
|
39
40
|
count = @materials.destroy_all.count
|
40
|
-
redirect_to admin_materials_path(query:
|
41
|
+
redirect_to admin_materials_path(query: @query_params), notice: t('.success', count: count)
|
41
42
|
end
|
42
43
|
|
43
44
|
def import_status
|
@@ -89,6 +90,14 @@ module Lcms
|
|
89
90
|
.map { |id| ::Lt::Lcms::Lesson::Downloader::Gdoc.gdoc_file_url(id) }
|
90
91
|
end
|
91
92
|
end
|
93
|
+
|
94
|
+
def set_query_params
|
95
|
+
@query_params = params[:query]
|
96
|
+
&.permit(
|
97
|
+
:course, :lesson, :name_date, :orientation, :search_term, :search_file_name, :sort_by, :title, :type,
|
98
|
+
:unit
|
99
|
+
) || {}
|
100
|
+
end
|
92
101
|
end
|
93
102
|
end
|
94
103
|
end
|
@@ -5,11 +5,12 @@ module Lcms
|
|
5
5
|
module Admin
|
6
6
|
class StandardsController < AdminController
|
7
7
|
before_action :find_standard, except: [:index]
|
8
|
+
before_action :set_query_params
|
8
9
|
|
9
10
|
def edit; end
|
10
11
|
|
11
12
|
def index
|
12
|
-
@query = OpenStruct.new
|
13
|
+
@query = OpenStruct.new @query_params
|
13
14
|
|
14
15
|
scope = Standard.order(:id)
|
15
16
|
scope = scope.search_by_name(@query.name) if @query.name.present?
|
@@ -31,6 +32,10 @@ module Lcms
|
|
31
32
|
@standard = Standard.find(params[:id])
|
32
33
|
end
|
33
34
|
|
35
|
+
def set_query_params
|
36
|
+
@query_params = params[:query]&.permit(:name) || {}
|
37
|
+
end
|
38
|
+
|
34
39
|
def standard_params
|
35
40
|
params.require(:standard).permit(:description)
|
36
41
|
end
|
@@ -5,9 +5,10 @@ module Lcms
|
|
5
5
|
module Admin
|
6
6
|
class UsersController < AdminController
|
7
7
|
before_action :find_user, except: %i(index new create)
|
8
|
+
before_action :set_query_params
|
8
9
|
|
9
10
|
def index
|
10
|
-
@query = OpenStruct.new
|
11
|
+
@query = OpenStruct.new @query_params
|
11
12
|
@users = users(@query)
|
12
13
|
end
|
13
14
|
|
@@ -52,6 +53,10 @@ module Lcms
|
|
52
53
|
@user = User.find(params[:id])
|
53
54
|
end
|
54
55
|
|
56
|
+
def set_query_params
|
57
|
+
@query_params = params[:query]&.permit(:access_code, :email) || {}
|
58
|
+
end
|
59
|
+
|
55
60
|
def user_params
|
56
61
|
params.require(:user).permit(:access_code, :email, :name, :role)
|
57
62
|
end
|
@@ -12,31 +12,42 @@ module Lcms
|
|
12
12
|
|
13
13
|
attr_reader :model
|
14
14
|
|
15
|
+
class << self
|
16
|
+
def grades
|
17
|
+
::Lcms::Engine::Grades::GRADES
|
18
|
+
end
|
19
|
+
|
20
|
+
def grades_abbrevs
|
21
|
+
::Lcms::Engine::Grades::GRADES_ABBR
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
15
25
|
def initialize(model)
|
16
26
|
@model = model
|
17
27
|
end
|
18
28
|
|
19
29
|
def list
|
20
|
-
@list ||=
|
30
|
+
@list ||= case model
|
31
|
+
when Resource
|
21
32
|
Array.wrap model.metadata['grade']
|
22
|
-
|
33
|
+
when Search::Document
|
23
34
|
Array.wrap model.grade.presence
|
24
35
|
else
|
25
36
|
model.grade_list
|
26
|
-
end.sort_by { |g|
|
37
|
+
end.sort_by { |g| self.class.grades.index(g) }
|
27
38
|
end
|
28
39
|
|
29
40
|
def average(abbr: true)
|
30
41
|
return nil if average_number.nil?
|
31
42
|
|
32
|
-
avg =
|
43
|
+
avg = self.class.grades[average_number]
|
33
44
|
abbr ? (grade_abbr(avg) || 'base') : avg
|
34
45
|
end
|
35
46
|
|
36
47
|
def average_number
|
37
48
|
return nil if list.empty?
|
38
49
|
|
39
|
-
list.map { |g|
|
50
|
+
list.map { |g| self.class.grades.index(g) }.sum / (list.size.nonzero? || 1)
|
40
51
|
end
|
41
52
|
|
42
53
|
def grade_abbr(abbr)
|
@@ -62,7 +73,7 @@ module Lcms
|
|
62
73
|
abbr = grade_abbr(g).upcase
|
63
74
|
|
64
75
|
# if the current grade is subsequent we store on the same chain
|
65
|
-
if idx.zero? ||
|
76
|
+
if idx.zero? || self.class.grades.index(g) == self.class.grades.index(prev) + 1
|
66
77
|
chain << abbr
|
67
78
|
else
|
68
79
|
# the grade is not subsequent, so we store the current chain, and create a new one
|
@@ -26,7 +26,7 @@ module Lcms
|
|
26
26
|
when /vimeo\.com/
|
27
27
|
url.match(%r{https?://(www\.)?vimeo.com/(\d+)}).try(:[], 2)
|
28
28
|
when /youtu\.be/
|
29
|
-
url.match(%r{https?://(www\.)?youtu\.be/([^"
|
29
|
+
url.match(%r{https?://(www\.)?youtu\.be/([^"&?/]+)}).try(:[], 2)
|
30
30
|
end
|
31
31
|
end
|
32
32
|
end
|
@@ -13,7 +13,7 @@ module Lcms
|
|
13
13
|
validates_presence_of :link, if: -> { link_fs.blank? }
|
14
14
|
validates_presence_of :link_fs, if: -> { link.blank? }
|
15
15
|
|
16
|
-
attr_reader :document
|
16
|
+
attr_reader :document, :service_errors
|
17
17
|
|
18
18
|
def initialize(attributes = {}, opts = {})
|
19
19
|
@is_reimport = attributes.delete(:reimport).present? || false
|
@@ -29,7 +29,7 @@ module Lcms
|
|
29
29
|
@document.update(reimported: true)
|
30
30
|
rescue StandardError => e
|
31
31
|
@document&.update(reimported: false)
|
32
|
-
Rails.logger.error e.message
|
32
|
+
Rails.logger.error "#{e.message}\n #{e.backtrace.join("\n ")}"
|
33
33
|
errors.add(:link, e.message)
|
34
34
|
false
|
35
35
|
end
|
@@ -45,18 +45,23 @@ module Lcms
|
|
45
45
|
def build_document
|
46
46
|
service = document_build_service
|
47
47
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
48
|
+
result =
|
49
|
+
if is_reimport
|
50
|
+
doc = service.build_for(link)
|
51
|
+
doc = service.build_for(link_fs, expand: true) if link_fs.present?
|
52
|
+
doc
|
53
|
+
elsif (full_doc = find_full_document)
|
54
|
+
# if there is a document with the same file_id or foundational_file_id
|
55
|
+
# we need to make full re-import to correctly handle expand process
|
56
|
+
service.build_for(full_doc.file_url)
|
57
|
+
service.build_for(full_doc.file_fs_url, expand: true)
|
58
|
+
else
|
59
|
+
service.build_for link
|
60
|
+
end
|
61
|
+
|
62
|
+
@service_errors = service.errors
|
63
|
+
|
64
|
+
result
|
60
65
|
end
|
61
66
|
|
62
67
|
def document_build_service
|
@@ -11,7 +11,7 @@ module Lcms
|
|
11
11
|
attribute :source_type, String
|
12
12
|
validates :link, presence: true
|
13
13
|
|
14
|
-
attr_accessor :material
|
14
|
+
attr_accessor :material, :service_errors
|
15
15
|
|
16
16
|
def initialize(attributes = {}, opts = {})
|
17
17
|
super(attributes)
|
@@ -28,12 +28,13 @@ module Lcms
|
|
28
28
|
}.compact
|
29
29
|
service = MaterialBuildService.new google_credentials, params
|
30
30
|
@material = service.build link
|
31
|
+
@service_errors = service.errors
|
31
32
|
|
32
33
|
material.update preview_links: {}
|
33
34
|
after_reimport_hook
|
34
35
|
true
|
35
36
|
rescue StandardError => e
|
36
|
-
Rails.logger.error e.message
|
37
|
+
Rails.logger.error "#{e.message}\n #{e.backtrace.join("\n ")}"
|
37
38
|
errors.add(:link, e.message)
|
38
39
|
false
|
39
40
|
end
|
@@ -2,9 +2,9 @@
|
|
2
2
|
|
3
3
|
module Admin
|
4
4
|
module ComponentsHelper
|
5
|
-
HIDDEN_FIELD_RE = /\b(?<=name=")[^"]+(?=")
|
5
|
+
HIDDEN_FIELD_RE = /\b(?<=name=")[^"]+(?=")/.freeze
|
6
6
|
|
7
|
-
def resource_picker_field(form, collection,
|
7
|
+
def resource_picker_field(form, collection, path:, name:, allow_multiple: true)
|
8
8
|
path = path.to_s
|
9
9
|
|
10
10
|
base_name = form.hidden_field(name).scan(HIDDEN_FIELD_RE)[0]
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import React from 'react'
|
2
|
+
import _ from 'lodash'
|
2
3
|
|
3
4
|
class ImportStatus extends React.Component {
|
4
5
|
constructor(props) {
|
@@ -53,7 +54,8 @@ class ImportStatus extends React.Component {
|
|
53
54
|
if (this.withPdf) {
|
54
55
|
return (
|
55
56
|
<a href={job.link}
|
56
|
-
className="o-adm-materials__resource
|
57
|
+
className="o-adm-materials__resource button primary u-margin-left--small u-margin-bottom--zero" target="_blank">
|
58
|
+
<i className="far fa-file-pdf"></i>
|
57
59
|
</a>
|
58
60
|
)
|
59
61
|
}
|
@@ -69,7 +71,8 @@ class ImportStatus extends React.Component {
|
|
69
71
|
return _.map(this.links, (link, idx) => (
|
70
72
|
<a key={`pl-${idx}`}
|
71
73
|
href={linkWithParams(link, { id: job.model.id })}
|
72
|
-
className="o-adm-materials__resource
|
74
|
+
className="o-adm-materials__resource button primary u-margin-left--small u-margin-bottom--zero" target="_blank">
|
75
|
+
<i className="fas fa-eye"></i>
|
73
76
|
</a>
|
74
77
|
))
|
75
78
|
}
|
@@ -99,7 +102,8 @@ class ImportStatus extends React.Component {
|
|
99
102
|
{job.status !== 'done' ? this.spinner() : null}
|
100
103
|
{job.status === 'done' && job.ok ? <span>{this.resourceButton(job)}</span> : null}
|
101
104
|
</div>
|
102
|
-
{job.errors ? (<p dangerouslySetInnerHTML={{__html: _.join(job.errors, '<br/>')}}></p>) : null}
|
105
|
+
{!(_.isEmpty(job.errors)) ? (<p dangerouslySetInnerHTML={{__html: _.join(job.errors, '<br/>')}}></p>) : null}
|
106
|
+
{!(_.isEmpty(job.warnings)) ? (<p dangerouslySetInnerHTML={{ __html: _.join(job.warnings, '<br/>') }}></p>) : null}
|
103
107
|
</li>
|
104
108
|
)
|
105
109
|
})
|