diff --git a/.env b/.env index b11bc3d..b24b3fc 100644 --- a/.env +++ b/.env @@ -12,3 +12,8 @@ REDFLAGS_FAQ_PAGE_ID=4410 REDFLAGS_STATS_PAGE_ID=8778 ROLLBAR_ACCESS_TOKEN= + +GOOGLE_APPLICATION_CREDENTIALS= +GOOGLE_SHEET_ID= +GOOGLE_SHEET_EXPORT_ID= +GOOGLE_SHEET_SCRIPT_URL= diff --git a/Gemfile b/Gemfile index 0961090..d1e5e00 100644 --- a/Gemfile +++ b/Gemfile @@ -48,6 +48,11 @@ gem 'annotate' gem 'rollbar' gem 'oj' +# Use Google API gems +gem 'google-apis-docs_v1' +gem 'google-apis-drive_v3' +gem 'google-apis-sheets_v4' + group :development, :test do gem 'dotenv-rails' gem 'rspec-rails' diff --git a/Gemfile.lock b/Gemfile.lock index 1d21add..b664aaa 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -46,6 +46,7 @@ GEM arel (8.0.0) autoprefixer-rails (9.7.6) execjs + base64 (0.2.0) bindex (0.8.1) bootstrap (4.0.0) autoprefixer-rails (>= 6.0.3) @@ -75,10 +76,11 @@ GEM crack (0.4.3) safe_yaml (~> 1.0.0) crass (1.0.6) + declarative (0.0.20) diff-lcs (1.3) - discourse_api (0.40.0) - faraday (~> 0.9) - faraday_middleware (~> 0.10) + discourse_api (1.1.0) + faraday (~> 1.0) + faraday_middleware (~> 1.0) rack (>= 1.6) docile (1.3.2) dotenv (2.7.5) @@ -93,17 +95,41 @@ GEM factory_bot_rails (5.2.0) factory_bot (~> 5.2.0) railties (>= 4.2.0) - faraday (0.17.3) + faraday (1.0.1) multipart-post (>= 1.2, < 3) - faraday_middleware (0.14.0) - faraday (>= 0.7.4, < 1.0) + faraday_middleware (1.2.0) + faraday (~> 1.0) ffi (1.12.2) font-awesome-rails (4.7.0.5) railties (>= 3.2, < 6.1) foreman (0.87.1) globalid (0.4.2) activesupport (>= 4.2.0) + google-apis-core (0.15.0) + addressable (~> 2.5, >= 2.5.1) + googleauth (~> 1.9) + httpclient (>= 2.8.1, < 3.a) + mini_mime (~> 1.0) + representable (~> 3.0) + retriable (>= 2.0, < 4.a) + rexml + google-apis-docs_v1 (0.27.0) + google-apis-core (>= 0.15.0, < 2.a) + google-apis-drive_v3 (0.51.0) + google-apis-core (>= 0.15.0, < 2.a) + google-apis-sheets_v4 (0.32.0) + google-apis-core (>= 0.15.0, < 2.a) + google-cloud-env (2.1.1) + faraday (>= 1.0, < 3.a) + googleauth (1.11.0) + faraday (>= 1.0, < 3.a) + google-cloud-env (~> 2.1) + jwt (>= 1.4, < 3.0) + multi_json (~> 1.11) + os (>= 0.9, < 2.0) + signet (>= 0.16, < 2.a) hashdiff (1.0.1) + httpclient (2.8.3) i18n (1.8.2) concurrent-ruby (~> 1.0) jbuilder (2.10.0) @@ -113,6 +139,8 @@ GEM railties (>= 4.2.0) thor (>= 0.14, < 2.0) json (2.3.0) + jwt (2.8.1) + base64 kaminari (1.2.1) activesupport (>= 4.1.0) kaminari-actionview (= 1.2.1) @@ -138,14 +166,16 @@ GEM mini_mime (1.0.2) mini_portile2 (2.7.1) minitest (5.14.1) + multi_json (1.15.0) multipart-post (2.1.1) mustermann (1.1.1) ruby2_keywords (~> 0.0.1) - nio4r (2.5.8) + nio4r (2.7.3) nokogiri (1.13.1) mini_portile2 (~> 2.7.0) racc (~> 1.4) oj (3.10.6) + os (1.1.4) pg (0.21.0) popper_js (1.16.0) public_suffix (4.0.6) @@ -190,6 +220,13 @@ GEM rb-inotify (0.10.1) ffi (~> 1.0) regexp_parser (1.7.0) + representable (3.2.0) + declarative (< 0.1.0) + trailblazer-option (>= 0.1.1, < 0.2.0) + uber (< 0.2.0) + retriable (3.1.2) + rexml (3.2.8) + strscan (>= 3.0.9) rollbar (2.25.0) rspec-core (3.9.2) rspec-support (~> 3.9.3) @@ -222,6 +259,11 @@ GEM sprockets (>= 2.8, < 4.0) sprockets-rails (>= 2.0, < 4.0) tilt (>= 1.1, < 3) + signet (0.19.0) + addressable (~> 2.8) + faraday (>= 0.17.5, < 3.a) + jwt (>= 1.5, < 3.0) + multi_json (~> 1.10) simplecov (0.17.1) docile (~> 1.1) json (>= 1.8, < 3) @@ -243,14 +285,17 @@ GEM actionpack (>= 4.0) activesupport (>= 4.0) sprockets (>= 3.0.0) + strscan (3.1.0) thor (1.0.1) thread_safe (0.3.6) tilt (2.0.10) + trailblazer-option (0.1.2) turbolinks (5.2.1) turbolinks-source (~> 5.2) turbolinks-source (5.2.0) tzinfo (1.2.7) thread_safe (~> 0.1) + uber (0.1.0) uglifier (4.2.0) execjs (>= 0.3.0, < 3) vcr (6.0.0) @@ -284,6 +329,9 @@ DEPENDENCIES factory_bot_rails font-awesome-rails foreman + google-apis-docs_v1 + google-apis-drive_v3 + google-apis-sheets_v4 jbuilder (~> 2.5) jquery-rails kaminari diff --git a/app/assets/javascripts/projects.js b/app/assets/javascripts/projects.js index 002297e..e271ce9 100644 --- a/app/assets/javascripts/projects.js +++ b/app/assets/javascripts/projects.js @@ -2,3 +2,22 @@ $(document).on('ready turbolinks:load', function () { $('[data-toggle="tooltip"]').tooltip(); $('.tag-tooltip').tooltip(); }); + +$(document).ready(function(){ + $('[data-toggle="collapse"]').on('click', function() { + $(this).find('.collapsed,.expanded').toggleClass('d-none'); + }); +}); + +document.addEventListener("turbolinks:load", function() { + document.addEventListener("turbolinks:load", function() { + if (!window.printCalled && window.location.pathname.endsWith("/pdf")) { + window.printCalled = true; + window.print(); + } + }); + + document.addEventListener("turbolinks:before-render", function() { + window.printCalled = false; + }); +}); diff --git a/app/controllers/admin/pages_controller.rb b/app/controllers/admin/pages_controller.rb index 4bb7cf7..6d2c907 100644 --- a/app/controllers/admin/pages_controller.rb +++ b/app/controllers/admin/pages_controller.rb @@ -2,59 +2,67 @@ class Admin::PagesController < AdminController before_action :load_page, only: [:show, :preview, :publish, :unpublish, :sync_one] def index - @pages = Page.order(id: :desc).page(params[:page]) - @projects = @pages.map { |page| Project.find_by(page: page) } + @pages = Page.includes(phase: :project).order(id: :desc) + @projects = @pages.map { |page| page.phase.project }.uniq end def show @revisions = @page.revisions.order(version: :desc).page(params[:page]) - @project = Project.find_by(page: @page) + @project = @page.phase.project end def preview - @project = Project.find_by(page: @page) + @phase = @page.phase if @project.present? if params['version'] == 'latest' - @revision = ProjectRevision.find_by!(revision: @page.latest_revision) + @phase_revision = @phase.revisions.find_by!(revision: @page.latest_revision) else - @revision = ProjectRevision.joins(:project, :revision).find_by!(projects: { page: @page }, revisions: { version: params['version'] }) + @phase_revision = @phase.revisions.find_by!(revision: @page.revisions.where(version: params['version'])) end - @ratings_by_type = @revision.ratings.index_by(&:rating_type) + @ratings_by_type = @phase.revisions.ratings.index_by(&:rating_type) else if params['version'] == 'latest' - @revision = @page.latest_revision + @phase_revision = PhaseRevision.find_by(revision: @page.latest_revision) else - @revision = @page.revisions.find_by!(version: params['version']) + @phase_revision = PhaseRevision.joins(:revision) + .find_by(revisions: { version: params['version'] }) end end end def publish if params['version'] == 'latest' - @page.update!(published_revision: @page.latest_revision) + revision = @page.latest_revision + @page.update!(published_revision: revision) else - @page.update!(published_revision: @page.revisions.find_by!(version: params['version'])) + revision = @page.revisions.find_by!(version: params['version']) + @page.update!(published_revision: revision) end + @page.publish_and_enqueue_jobs(revision) + redirect_back fallback_location: { action: :index } end def unpublish + revision_id = @page.published_revision.id if @page.published_revision.present? + @page.update!(published_revision: nil) + @page.unpublish_and_enqueue_jobs(revision_id) redirect_back fallback_location: { action: :index } end def sync - SyncCategoryTopicsJob.perform_later(ENV.fetch('REDFLAGS_CATEGORY_SLUG')) + SyncAllTopicsJob.perform_later redirect_back fallback_location: { action: :index } end def sync_one - SyncTopicJob.perform_later(@page.id) + SyncOneTopicJob.perform_later(@page.id) redirect_back fallback_location: { action: :index } end diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb index 70cae5c..a7403a2 100644 --- a/app/controllers/admin/projects_controller.rb +++ b/app/controllers/admin/projects_controller.rb @@ -1,18 +1,9 @@ class Admin::ProjectsController < AdminController - before_action :load_project, only: [:update] - - def update - @project.update!(project_params) - redirect_to admin_page_path(@project.page) - end + before_action :load_project private def load_project @project = Project.find(params[:id]) end - - def project_params - params.require(:project).permit(:category) - end end diff --git a/app/controllers/phase_revision_controller.rb b/app/controllers/phase_revision_controller.rb new file mode 100644 index 0000000..82a4b62 --- /dev/null +++ b/app/controllers/phase_revision_controller.rb @@ -0,0 +1,43 @@ +class PhaseRevisionController < ApplicationController + def show + @project = Project.find_by!(id: params[:project_id]) + @phase_revision = PhaseRevision.find_published_revision(@project.id, params[:revision_type]) + + if @phase_revision + @revision = @phase_revision.revision + @ratings_by_type = @phase_revision.ratings.index_by(&:rating_type) + @metadata.og.title = @revision.title + @metadata.og.description = 'Kolaboratívne hodnotenie projektu metodikou Red Flags.' + + render :show + end + end + + def pdf + @project = Project.find_by!(id: params[:project_id]) + @phase_revision = PhaseRevision.find_published_revision(@project.id, params[:revision_type]) + + if @phase_revision + @revision = @phase_revision.revision + @ratings_by_type = @phase_revision.ratings.index_by(&:rating_type) + @metadata.og.title = @revision.title + @metadata.og.description = 'Kolaboratívne hodnotenie projektu metodikou Red Flags.' + + render layout: "no_header_footer" + end + end + + def show_history + @project = Project.find_by!(id: params[:project_id]) + @phase_revision = PhaseRevision.find_revision_history(@project.id, params[:revision_type], params[:version]) + + if @phase_revision + @revision = @phase_revision.revision + @ratings_by_type = @phase_revision.ratings.index_by(&:rating_type) + @metadata.og.title = @revision.title + @metadata.og.description = 'Kolaboratívne hodnotenie projektu metodikou Red Flags.' + + render :show + end + end +end diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 9d77b17..83b39cd 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -1,15 +1,7 @@ class ProjectsController < ApplicationController - def show - @project = Project.find(params[:id]).published_revision - @ratings_by_type = @project.ratings.index_by(&:rating_type) - @metadata.og.title = @project.title - @metadata.og.description = 'Kolaboratívne hodnotenie projektu metodikou Red Flags.' - end def index @selected_tag = params[:tag] - @projects = Project.published - @projects = @projects.with_tag(params[:tag]) if ProjectsHelper::ALLOWED_TAGS.keys.include?(params[:tag]) - @projects = @projects.map { |p| p.published_revision }.sort_by(&:aggregated_rating) + @projects = Project.filtered_projects(@selected_tag, params[:sort]) end end diff --git a/app/controllers/static_controller.rb b/app/controllers/static_controller.rb index 32d9d3e..4cd8d44 100644 --- a/app/controllers/static_controller.rb +++ b/app/controllers/static_controller.rb @@ -1,9 +1,9 @@ class StaticController < ApplicationController def index - top_projects = Project.published.joins(:published_revision).limit(5) + top_project_revisions = PhaseRevision.where(published: true).joins(:revision).limit(5) - @good_projects = top_projects.good.order('redflags_count ASC, total_score::float / maximum_score DESC').map(&:published_revision) - @bad_projects = top_projects.bad.order('redflags_count DESC, total_score::float / maximum_score ASC').map(&:published_revision) + @good_projects = top_project_revisions.where('phase_revisions.redflags_count = ?', 0).order('phase_revisions.total_score::float / phase_revisions.maximum_score DESC') + @bad_projects = top_project_revisions.order('phase_revisions.redflags_count DESC, phase_revisions.total_score::float / phase_revisions.maximum_score ASC') end def about diff --git a/app/helpers/previews_helper.rb b/app/helpers/previews_helper.rb index c12c886..d8a7882 100644 --- a/app/helpers/previews_helper.rb +++ b/app/helpers/previews_helper.rb @@ -4,8 +4,11 @@ def page_preview?(page) end def revision_preview?(revision) - return true unless Project.exists?(page: revision.page) - project_revision = ProjectRevision.where(revision: revision).first - project_revision && !project_revision.total_score_percentage.nan? + return true unless revision.phase_revision&.phase&.project.present? + + phase_revision = revision.phase_revision + return false if phase_revision.nil? + + phase_revision.total_score.present? && !phase_revision.total_score_percentage.nan? end end diff --git a/app/jobs/export_topic_into_sheet_job.rb b/app/jobs/export_topic_into_sheet_job.rb new file mode 100644 index 0000000..883f2e8 --- /dev/null +++ b/app/jobs/export_topic_into_sheet_job.rb @@ -0,0 +1,171 @@ +class ExportTopicIntoSheetJob < ApplicationJob + queue_as :default + + COLUMN_NAMES = [ + 'Projekt', 'Projekt ID', 'ID hodnotenia prípravy', 'Link na hodnotenie prípravy', 'ID hodnotenia produktu', 'Link na hodnotenie produktu', + 'Názov-Príprava', 'Garant-Príprava', 'Stručný opis-Príprava', 'Náklady na projekt-Príprava', 'Aktuálny stav projektu-Príprava', + 'Čo sa práve deje-Príprava', 'Zhrnutie hodnotenia Red Flags-Príprava', 'Stanovisko Slovensko.Digital-Príprava', 'Reforma VS body-Príprava', + 'Reforma VS-Príprava', 'Merateľné ciele (KPI) body-Príprava', 'Merateľné ciele (KPI)-Príprava', 'Postup dosiahnutia cieľov body-Príprava', + 'Postup dosiahnutia cieľov-Príprava', 'Súlad s KRIT body-Príprava', 'Súlad s KRIT-Príprava', 'Biznis prínos body-Príprava', + 'Biznis prínos-Príprava', 'Príspevok v informatizácii body-Príprava', 'Príspevok v informatizácii-Príprava', 'Kalkulácia efektívnosti body-Príprava', + 'Kalkulácia efektívnosti-Príprava', 'Transparentnosť a participácia body-Príprava', 'Transparentnosť a participácia-Príprava', 'Názov-Produkt', + 'Garant-Produkt', 'Stručný opis-Produkt', 'Náklady na projekt-Produkt', 'Aktuálny stav projektu-Produkt', 'Čo sa práve deje-Produkt', + 'Zhrnutie hodnotenia Red Flags-Produkt', 'Stanovisko Slovensko.Digital-Produkt', 'Reforma VS body-Produkt', 'Reforma VS-Produkt', + 'Merateľné ciele (KPI) body-Produkt', 'Merateľné ciele (KPI)-Produkt', 'Postup dosiahnutia cieľov body-Produkt', 'Postup dosiahnutia cieľov-Produkt', + 'Súlad s KRIT body-Produkt', 'Súlad s KRIT-Produkt', 'Biznis prínos body-Produkt', 'Biznis prínos-Produkt', 'Príspevok v informatizácii body-Produkt', + 'Príspevok v informatizácii-Produkt', 'Kalkulácia efektívnosti body-Produkt', 'Kalkulácia efektívnosti-Produkt', 'Transparentnosť a participácia body-Produkt', + 'Transparentnosť a participácia-Produkt', 'Súlad s požiadavkami body-Produkt', 'Súlad s požiadavkami-Produkt', 'Elektronické služby body-Produkt', + 'Elektronické služby-Produkt', 'Identifikácia, autentifikácia, autorizácia (IAA) body-Produkt', 'Identifikácia, autentifikácia, autorizácia (IAA)-Produkt', + 'Riadenie údajov body-Produkt', 'Riadenie údajov-Produkt', 'OpenData body-Produkt', 'OpenData-Produkt', 'MyData body-Produkt', 'MyData-Produkt', + 'OpenAPI body-Produkt', 'OpenAPI-Produkt', 'Zdrojový kód body-Produkt', 'Zdrojový kód-Produkt' + ].freeze + + RATINGS_CONSTANT = { + "Reforma VS" => "Reforma VS body", + "Merateľné ciele (KPI)" => "Merateľné ciele (KPI) body", + "Postup dosiahnutia cieľov" => "Postup dosiahnutia cieľov body", + "Súlad s KRIT" => "Súlad s KRIT body", + "Biznis prínos" => "Biznis prínos body", + "Príspevok v informatizácii" => "Príspevok v informatizácii body", + "Kalkulácia efektívnosti" => "Kalkulácia efektívnosti body", + "Transparentnosť a participácia" => "Transparentnosť a participácia body", + "Súlad s požiadavkami" => "Súlad s požiadavkami body", + "Elektronické služby" => "Elektronické služby body", + "Identifikácia, autentifikácia, autorizácia (IAA)" => "Identifikácia, autentifikácia, autorizácia (IAA) body", + "Riadenie údajov" => "Riadenie údajov body", + "OpenData" => "OpenData body", + "MyData" => "MyData body", + "OpenAPI" => "OpenAPI body", + "Zdrojový kód" => "Zdrojový kód body" + } + + def perform(new_revision) + if new_revision.published? + update_sheet(new_revision) + else + delete_row(new_revision) + end + end + + private + + def update_sheet(new_revision) + suffix = new_revision.phase.phase_type.name == 'Prípravná fáza' ? '-Príprava' : '-Produkt' + + result = extract_content_from_html(new_revision.body_html) + ratings = new_revision.ratings.includes(:rating_type).index_by { |rating| rating.rating_type.name } + + result["Názov"] = new_revision.title + result["Garant"] = new_revision.guarantor + result["Stručný opis"] = new_revision.description + result["Náklady na projekt"] = new_revision.budget + result["Aktuálny stav projektu"] = new_revision.stage.name + result["Čo sa práve deje"] = new_revision.current_status + result["Zhrnutie hodnotenia Red Flags"] = new_revision.summary + result["Stanovisko Slovensko.Digital"] = new_revision.recommendation + + RATINGS_CONSTANT.each do |type_name, result_field_name| + result[result_field_name] = ratings[type_name]&.score || 'N/A' + end + + result.transform_keys! { |k| k + suffix } + + result["Projekt"] = new_revision.title + result["Projekt ID"] = new_revision.phase.project_id + + if new_revision.phase.phase_type.name == 'Prípravná fáza' + result["ID hodnotenia prípravy"] = new_revision.revision.page.id + result["Link na hodnotenie prípravy"] = %(=HYPERLINK("https://redflags.slovensko.digital/admin/pages/#{new_revision.revision.page.id}"; "Hodnotenie v adminovi")) + else + result["ID hodnotenia produktu"] = new_revision.revision.page.id + result["Link na hodnotenie produktu"] = %(=HYPERLINK("https://redflags.slovensko.digital/admin/pages/#{new_revision.revision.page.id}"; "Hodnotenie v adminovi")) + end + + sheets_service = GoogleApiService.get_sheets_service + response = sheets_service.get_spreadsheet_values(ENV['GOOGLE_SHEET_EXPORT_ID'], 'A:CA') + header_row = response.values[2] + current_row_count = response.values.count + column_indices = COLUMN_NAMES.map { |name| header_row.index(name) } + + values = COLUMN_NAMES.map { |name| result[name] } + + if find_row_index_by_project_id(response.values[3..], header_row, new_revision.phase.project_id) + range = "Hárok1!#{column_letter(column_indices.min + 1)}#{current_row_count}:#{column_letter(column_indices.max + 1)}#{current_row_count}" + else + range = "Hárok1!#{column_letter(column_indices.min + 1)}#{current_row_count + 1}:#{column_letter(column_indices.max + 1)}#{current_row_count + 1}" + end + update_google_sheet(sheets_service, ENV['GOOGLE_SHEET_EXPORT_ID'], column_indices, values, range) + end + + def delete_row(new_revision) + sheets_service = GoogleApiService.get_sheets_service + response = sheets_service.get_spreadsheet_values(ENV['GOOGLE_SHEET_EXPORT_ID'], 'A:BA') + header_row = response.values[2] + + row_index = find_row_index(response.values[3..-1], header_row, new_revision.revision.page.id) + + if row_index + delete_google_sheet_row(sheets_service, ENV['GOOGLE_SHEET_EXPORT_ID'], row_index + 3) + else + raise ArgumentError, "No data found for the given page_id in the spreadsheet. ID may not match or is not in string format." + end + end + + def extract_content_from_html(body_html) + result = {} + doc = Nokogiri::HTML(body_html) + doc.css('h3').each do |header| + siblings = header.xpath('following-sibling::*[not(self::div)]').take_while { |node| node.name != 'h3' } + content = siblings.map { |sibling| sibling.text.strip }.join(' ') + result[header.text.strip] = content + end + result + end + + def update_google_sheet(sheets_service, google_sheet_id, column_indices, values, range) + value_range_object = Google::Apis::SheetsV4::ValueRange.new(values: [values]) + sheets_service.update_spreadsheet_value(google_sheet_id, range, value_range_object, value_input_option: 'USER_ENTERED') + end + + def delete_google_sheet_row(sheets_service, google_sheet_id, row_index) + request_body = Google::Apis::SheetsV4::BatchUpdateSpreadsheetRequest.new( + requests: [ + { + delete_dimension: { + range: { + sheet_id: get_sheet_id(sheets_service, google_sheet_id), + dimension: 'ROWS', + start_index: row_index - 1, + end_index: row_index + } + } + } + ] + ) + sheets_service.batch_update_spreadsheet(google_sheet_id, request_body) + end + + def get_sheet_id(sheets_service, google_sheet_id) + spreadsheet = sheets_service.get_spreadsheet(google_sheet_id) + spreadsheet.sheets.first.properties.sheet_id + end + + def find_row_index(rows, header_row, page_id) + id_index = header_row.index('ID hodnotenia') + rows.find_index { |row| row[id_index] == page_id.to_s } + end + + def find_row_index_by_project_id(rows, header_row, project_id) + project_id_index = header_row.index('Projekt ID') + rows.find_index { |row| row[project_id_index] == project_id.to_s } + end + + def column_letter(number) + letter = '' + while number > 0 + number, remainder = (number - 1).divmod(26) + letter = (65 + remainder).chr + letter + end + letter + end +end diff --git a/app/jobs/initialization_of_topics_to_sheets_job.rb b/app/jobs/initialization_of_topics_to_sheets_job.rb new file mode 100644 index 0000000..dbc78d0 --- /dev/null +++ b/app/jobs/initialization_of_topics_to_sheets_job.rb @@ -0,0 +1,55 @@ +class InitializationOfTopicsToSheetsJob < ApplicationJob + queue_as :default + + COLUMN_NAMES = ['Projekt', 'Projekt ID', 'Platforma', 'Dátum poslednej aktualizácie', 'Draft prípravy', 'ID draft prípravy', 'ID prípravy', 'Príprava publikovaná?', 'Dátum publikácie prípravy', 'RF web príprava'].freeze + + def perform(topic_id, found_page, project) + uri = URI(ENV['GOOGLE_SHEET_SCRIPT_URL']) + Net::HTTP.get(uri) + + sheets_service = GoogleApiService.get_sheets_service + response_values = fetch_response_values(sheets_service) + column_indices = get_column_indices(response_values[2]) + + values = construct_values(topic_id, found_page, project) + + update_sheet_cells(sheets_service, column_indices, response_values.count, values) + + ExportTopicIntoSheetJob.set(wait: 15.seconds).perform_later(found_page.published_revision.phase_revision) if found_page.published_revision.present? + end + + private + + def fetch_response_values(sheets_service) + response = sheets_service.get_spreadsheet_values(ENV['GOOGLE_SHEET_ID'], 'A:Z') + response.values + end + + def get_column_indices(header_row) + COLUMN_NAMES.map { |name| header_row.index(name) } + end + + def construct_values(topic_id, found_page, project) + title_parametrized = found_page.latest_revision.title.parameterize + [ + found_page.latest_revision.title, + project.id, + %(=HYPERLINK("https://platforma.slovensko.digital/t/#{title_parametrized}/#{topic_id}"; "Platforma link")), + found_page.published_revision&.updated_at&.in_time_zone('Europe/Bratislava')&.strftime('%H:%M %d.%m.%Y') || '', + '', + '', + topic_id.to_s, + found_page.published_revision.present? ? 'Áno' : 'Nie', + found_page.published_revision&.updated_at&.in_time_zone('Europe/Bratislava')&.strftime('%H:%M %d.%m.%Y') || '', + found_page.published_revision.present? ? %(=HYPERLINK("https://redflags.slovensko.digital/admin/pages/#{found_page.id}"; "Admin link")) : '' + ] + end + + def update_sheet_cells(sheets_service, indices, current_row_count, values) + values.each_with_index do |value, idx| + range = "Hárok1!#{(indices[idx] + 65).chr}#{current_row_count}" + value_range_object = Google::Apis::SheetsV4::ValueRange.new(values: [[value]]) + sheets_service.update_spreadsheet_value(ENV['GOOGLE_SHEET_ID'], range, value_range_object, value_input_option: 'USER_ENTERED') + end + end +end \ No newline at end of file diff --git a/app/jobs/sync_all_topics_job.rb b/app/jobs/sync_all_topics_job.rb new file mode 100644 index 0000000..8c67207 --- /dev/null +++ b/app/jobs/sync_all_topics_job.rb @@ -0,0 +1,43 @@ +class SyncAllTopicsJob < ApplicationJob + queue_as :default + + COLUMN_NAMES = ['Projekt', 'Projekt ID', 'Platforma', 'ID draft prípravy', 'ID prípravy', 'ID draft produktu', 'ID produktu'].freeze + + def perform + sheets_service = GoogleApiService.get_sheets_service + response_values = sheets_service.get_spreadsheet_values(ENV.fetch('GOOGLE_SHEET_ID'), 'A:Z')&.values + + indices = find_indices(response_values[2]) + response_values[3..-1].each { |row| process_row(row, indices) } + end + + private + + def find_indices(header_row) + indices = COLUMN_NAMES.map { |name| [name, header_row.index(name)] }.to_h + return indices if indices.values.all? + + raise ArgumentError, "Could not find required columns in the spreadsheet." + end + + def process_row(row, indices) + project_name = row[indices["Projekt"]] + project_id = row[indices["Projekt ID"]] + platform_link = row[indices["Platforma"]] + preparation_document_id = row[indices["ID draft prípravy"]] + preparation_page_id = row[indices["ID prípravy"]] + product_document_id = row[indices["ID draft produktu"]] + product_page_id = row[indices["ID produktu"]] + + if platform_link != '' + SyncTopicJob.perform_later(project_id, preparation_page_id) + else + enqueue_job_for_update("#{project_name} - Príprava", project_id, preparation_document_id, preparation_page_id, 'Prípravná fáza') + end + enqueue_job_for_update("#{project_name} - Produkt", project_id, product_document_id, product_page_id, 'Fáza produkt') + end + + def enqueue_job_for_update(name, project_id, document_id, page_id, page_type) + SyncGoogleDocumentJob.perform_later(name, project_id, document_id, page_id, page_type) + end +end diff --git a/app/jobs/sync_category_topics_job.rb b/app/jobs/sync_category_topics_job.rb index fcaaf6d..5976dfd 100644 --- a/app/jobs/sync_category_topics_job.rb +++ b/app/jobs/sync_category_topics_job.rb @@ -9,7 +9,7 @@ def perform(category_slug, page = 0, sync_topic_job: SyncTopicJob) SyncCategoryTopicsJob.perform_later(category_slug, page + 1) if topics.count >= 30 topics.each do |topic| - sync_topic_job.perform_later(topic.fetch('id')) + sync_topic_job.perform_later(nil, topic.fetch('id')) end end end diff --git a/app/jobs/sync_google_document_job.rb b/app/jobs/sync_google_document_job.rb new file mode 100644 index 0000000..1d3453e --- /dev/null +++ b/app/jobs/sync_google_document_job.rb @@ -0,0 +1,72 @@ +class SyncGoogleDocumentJob < ApplicationJob + queue_as :default + + def perform(project_name, project_id, google_document_id, page_id, phase_type_name) + parsed_content = parse_google_doc(google_document_id) + create_or_update_page(project_name, project_id, google_document_id, page_id, phase_type_name, parsed_content) + end + + def create_or_update_page(project_name, project_id, google_document_id, page_id, phase_type_name, parsed_content) + Page.transaction do + page = Page.find_or_create_by!(id: page_id) do |new_page| + new_project = Project.find_or_create_by(id: project_id) + phase_type = PhaseType.find_by(name: phase_type_name) + new_phase = Phase.find_or_create_by(project: new_project, phase_type: phase_type) + new_page.phase = new_phase + end + + drive_service = GoogleApiService.get_drive_service + revisions = drive_service.list_revisions(google_document_id)&.revisions&.count + version = revisions + + revision = page.revisions.find_or_initialize_by(version: version) + revision.title = project_name + revision.raw = parsed_content + revision.save! + + page.latest_revision = revision + page.save! + end + +=begin + Page.transaction do + page = setup_page(project_id, page_id) + revision = setup_revision(page, project_name, parsed_content, google_document_id) + update_page_info(page, page_type, revision) + end +=end + end + + def setup_page(project_id, page_id) + Page.find_or_create_by!(id: page_id) do |new_page| + new_project = Project.find_or_create_by!(id: project_id) + new_page.project = new_project + end + end + + def setup_revision(page, project_name, parsed_content, google_document_id) + drive_service = GoogleApiService.get_drive_service + revisions = drive_service.list_revisions(google_document_id)&.revisions&.count + version = revisions + revision = page.revisions.find_or_initialize_by(version: version) + revision.title = project_name + revision.raw = parsed_content + revision.load_ratings(parsed_content) + revision.save! + revision + end + + def update_page_info(page, page_type, revision) + page.latest_revision = revision + page.page_type = page_type + page.save! + end + + def parse_google_doc(google_document_id) + document = GoogleApiService.get_document(google_document_id) + parser_service = DocumentParserService.new(document) + + html_content = parser_service.to_html + parser_service.to_hash(html_content) + end +end \ No newline at end of file diff --git a/app/jobs/sync_one_topic_job.rb b/app/jobs/sync_one_topic_job.rb new file mode 100644 index 0000000..460fcc7 --- /dev/null +++ b/app/jobs/sync_one_topic_job.rb @@ -0,0 +1,50 @@ +class SyncOneTopicJob < ApplicationJob + queue_as :default + + COLUMN_NAMES = ['Projekt', 'Projekt ID', 'Platforma', 'ID draft prípravy', 'ID prípravy', 'ID draft produktu', 'ID produktu'].freeze + + def perform(page_id) + sheets_service = GoogleApiService.get_sheets_service + response_values = sheets_service.get_spreadsheet_values(ENV.fetch('GOOGLE_SHEET_ID'), 'A:Z')&.values + + indices = find_indices(response_values[2]) + row = response_values[3..-1].find do |row| + row[indices["ID prípravy"]] == page_id.to_s || row[indices["ID produktu"]] == page_id.to_s + end + + process_row(row, indices, page_id) if row + end + + private + + def find_indices(header_row) + indices = COLUMN_NAMES.map { |name| [name, header_row.index(name)] }.to_h + return indices if indices.values.all? + + raise ArgumentError, "Could not find required columns in the spreadsheet." + end + + def process_row(row, indices, target_id) + project_name = row[indices["Projekt"]] + project_id = row[indices["Projekt ID"]] + platform_link = row[indices["Platforma"]] + preparation_document_id = row[indices["ID draft prípravy"]] + preparation_page_id = row[indices["ID prípravy"]] + product_document_id = row[indices["ID draft produktu"]] + product_page_id = row[indices["ID produktu"]] + + if platform_link != '' + SyncTopicJob.perform_later(project_id, preparation_page_id) + else + if target_id == preparation_page_id.to_i + enqueue_job_for_update("#{project_name} - Príprava", project_id, preparation_document_id, preparation_page_id, 'Prípravná fáza') + else target_id == product_page_id.to_i + enqueue_job_for_update("#{project_name} - Produkt", project_id, product_document_id, product_page_id, 'Fáza produkt') + end + end + end + + def enqueue_job_for_update(name, project_id, document_id, page_id, page_type) + SyncGoogleDocumentJob.perform_later(name, project_id, document_id, page_id, page_type) + end +end diff --git a/app/jobs/sync_revision_job.rb b/app/jobs/sync_revision_job.rb index 920b262..5a664b5 100644 --- a/app/jobs/sync_revision_job.rb +++ b/app/jobs/sync_revision_job.rb @@ -3,13 +3,14 @@ class SyncRevisionJob < ApplicationJob def perform(revision) return if revision.raw['category_id'] != ENV.fetch('REDFLAGS_PROJECTS_CATEGORY_ID').to_i - Project.transaction do + + Phase.transaction do page = revision.page - project = Project.find_or_create_by!(id: page.id, page_id: page.id) + phase = page.phase - project_revision = project.revisions.find_or_initialize_by(revision_id: revision.id) - project_revision.load_from_data(revision.raw) - project_revision.save! + phase_revision = phase.revisions.find_or_initialize_by(revision_id: revision.id) + phase_revision.load_from_data(revision.raw) + phase_revision.save! end end end diff --git a/app/jobs/sync_topic_job.rb b/app/jobs/sync_topic_job.rb index febe201..ac32ef9 100644 --- a/app/jobs/sync_topic_job.rb +++ b/app/jobs/sync_topic_job.rb @@ -1,15 +1,20 @@ class SyncTopicJob < ApplicationJob queue_as :default - def perform(topic_id) + def perform(project_id, topic_id) client = DiscourseApi::Client.new(ENV.fetch('REDFLAGS_DISCOURSE_URL')) topic = client.topic(topic_id) + page = nil + project = nil Page.transaction do + page = Page.find_or_create_by!(id: topic_id) do |new_page| + project = Project.find_or_create_by(id: project_id) + phase_type = PhaseType.find_by(name: 'Prípravná fáza') + new_phase = Phase.find_or_create_by(project: project, phase_type: phase_type) + new_page.phase = new_phase + end - # TODO latest_revision_id should be not null on DB level - - page = Page.find_or_create_by!(id: topic_id) version = topic['post_stream']['posts'].first['version'] revision = page.revisions.find_or_initialize_by(version: version) @@ -21,5 +26,8 @@ def perform(topic_id) page.latest_revision = revision page.save! end + + # For initial import of current topics into Google Sheets + #InitializationOfTopicsToSheetsJob.set(wait: 15.seconds).perform_later(topic_id, page, project) end end diff --git a/app/jobs/update_multiple_sheet_columns_job.rb b/app/jobs/update_multiple_sheet_columns_job.rb new file mode 100644 index 0000000..c489b47 --- /dev/null +++ b/app/jobs/update_multiple_sheet_columns_job.rb @@ -0,0 +1,18 @@ +class UpdateMultipleSheetColumnsJob < ApplicationJob + queue_as :default + + def perform(page_id, updates) + updates.each do |update| + column_names = update[:column_names] + page_type = update[:page_type] + published_value = update[:published_value] + + UpdateSheetValueJob.perform_now( + page_id, + column_names, + page_type, + published_value + ) + end + end +end \ No newline at end of file diff --git a/app/jobs/update_sheet_value_job.rb b/app/jobs/update_sheet_value_job.rb new file mode 100644 index 0000000..1f361c6 --- /dev/null +++ b/app/jobs/update_sheet_value_job.rb @@ -0,0 +1,54 @@ +class UpdateSheetValueJob < ApplicationJob + queue_as :default + + REQUIRED_COLUMNS = ['ID prípravy', 'ID produktu'].freeze + + def perform(page_id, column_names, page_type, published_value) + sheets_service = GoogleApiService.get_sheets_service + response_values = sheets_service.get_spreadsheet_values(ENV['GOOGLE_SHEET_ID'], 'A:Z')&.values + + header_row = response_values[2] + indices = find_indices(header_row) + + row_index = find_row_index(response_values[3..-1], indices, page_id) + if row_index.nil? + raise ArgumentError, "No data found for the given page_id in the spreadsheet. ID may not match or is not in string format." + end + + match_column_name = column_names[page_type] + if match_column_name + handle_row(sheets_service, ENV['GOOGLE_SHEET_ID'], header_row, row_index, match_column_name, published_value) + else + raise ArgumentError, "No matching column for page type." + end + end + + private + + def find_indices(header_row) + indices = REQUIRED_COLUMNS.map { |name| [name, header_row.index(name)] }.to_h + return indices if indices.values.all? + + raise ArgumentError, "Could not find required columns in the spreadsheet." + end + + def find_row_index(rows, indices, page_id) + rows.find_index do |row| + row[indices['ID prípravy']] == page_id.to_s || row[indices['ID produktu']] == page_id.to_s + end + end + + def handle_row(sheets_service, google_sheet_id, header_row, row_index, column_name, published_value) + column_index = header_row.index(column_name) + raise ArgumentError, "Could not find the provided column in the spreadsheet." if column_index.nil? + + update_google_sheet(sheets_service, google_sheet_id, row_index, column_index, published_value) + end + + def update_google_sheet(sheets_service, google_sheet_id, row, column_index, value) + range = "Hárok1!#{(column_index + 65).chr}#{row + 4}" + value_range_object = Google::Apis::SheetsV4::ValueRange.new(values: [[value]]) + + sheets_service.update_spreadsheet_value(google_sheet_id, range, value_range_object, value_input_option: 'USER_ENTERED') + end +end diff --git a/app/models/page.rb b/app/models/page.rb index 7128ee4..1e2aef7 100644 --- a/app/models/page.rb +++ b/app/models/page.rb @@ -15,6 +15,8 @@ # class Page < ApplicationRecord + belongs_to :phase + has_many :revisions belongs_to :published_revision, class_name: 'Revision', optional: true @@ -22,8 +24,6 @@ class Page < ApplicationRecord delegate :title, to: :latest_revision - after_save :schedule_sync_project_job - def publishable? true end @@ -36,9 +36,76 @@ def synced? published_revision == latest_revision end - private + def publish_and_enqueue_jobs(revision) + new_revision = update_associated_phase_revision(revision) + + if new_revision + updates = build_publish_updates(new_revision) + UpdateMultipleSheetColumnsJob.perform_later(id, updates) + ExportTopicIntoSheetJob.perform_later(new_revision) + end + end + + def update_associated_phase_revision(revision) + related_revisions = PhaseRevision.where(revision_id: revisions.ids).where.not(revision_id: revision.id) + related_revisions.update_all(published: false) if revision + + new_revision = PhaseRevision.find_by(revision_id: revision.id) + if new_revision + new_revision.update!(published: true, was_published: true, published_at: Time.now) + new_revision + end + end + + def build_publish_updates(revision) + [ + { + column_names: { "Prípravná fáza" => "Príprava publikovaná?", "Fáza produkt" => "Produkt publikovaný?" }, + page_type: revision.phase.phase_type.name, + published_value: "Áno" + }, + { + column_names: { "Prípravná fáza" => "Dátum publikácie prípravy", "Fáza produkt" => "Dátum publikácie produktu" }, + page_type: revision.phase.phase_type.name, + published_value: revision.published_at.in_time_zone('Europe/Bratislava').strftime('%H:%M %d.%m.%Y') + }, + { + column_names: { "project" => "Dátum poslednej aktualizácie" }, + page_type: "project", + published_value: revision.published_at.in_time_zone('Europe/Bratislava').strftime('%H:%M %d.%m.%Y') + }, + { + column_names: { "Prípravná fáza" => "RF web príprava", "Fáza produkt" => "RF web produkt" }, + page_type: revision.phase.phase_type.name, + published_value: %(=HYPERLINK("https://redflags.slovensko.digital/admin/pages/#{id}"; "Admin link")) + } + ] + end + + def unpublish_and_enqueue_jobs(revision_id) + PhaseRevision.where(revision_id: revision_id).update_all(published: false, published_at: nil) if revision_id + + updates = build_unpublish_updates + UpdateMultipleSheetColumnsJob.perform_later(id, updates) + end - def schedule_sync_project_job - SyncProjectJob.perform_later(self) + def build_unpublish_updates + [ + { + column_names: { "Prípravná fáza" => "Príprava publikovaná?", "Fáza produkt" => "Produkt publikovaný?" }, + page_type: phase.phase_type.name, + published_value: "Nie" + }, + { + column_names: { "Prípravná fáza" => "Dátum publikácie prípravy", "Fáza produkt" => "Dátum publikácie produktu" }, + page_type: phase.phase_type.name, + published_value: "" + }, + { + column_names: { "Prípravná fáza" => "RF web príprava", "Fáza produkt" => "RF web produkt" }, + page_type: phase.phase_type.name, + published_value: "" + } + ] end end diff --git a/app/models/phase.rb b/app/models/phase.rb new file mode 100644 index 0000000..9687a8c --- /dev/null +++ b/app/models/phase.rb @@ -0,0 +1,29 @@ + # == Schema Information + # + # Table name: phases + # + # id :integer not null, primary key + # created_at :datetime not null + # updated_at :datetime not null + # + + class Phase < ApplicationRecord + belongs_to :project + belongs_to :phase_type + + has_many :pages + has_many :revisions, class_name: 'PhaseRevision' + + has_one :published_revision, -> { where(published: true) }, class_name: 'PhaseRevision' + + def phase_type_label + case phase_type.name + when "Prípravná fáza" + "Hodnotenie prípravy" + when "Fáza produkt" + "Hodnotenie produktu" + else + phase_type + end + end + end diff --git a/app/models/phase_revision.rb b/app/models/phase_revision.rb new file mode 100644 index 0000000..53bb001 --- /dev/null +++ b/app/models/phase_revision.rb @@ -0,0 +1,215 @@ +# == Schema Information +# +# Table name: project_revisions +# +# id :integer not null, primary key +# project_id :integer not null +# revision_id :integer not null +# title :string not null +# full_name :string +# guarantor :string +# description :string +# budget :string +# created_at :datetime not null +# updated_at :datetime not null +# body_html :string +# total_score :integer +# maximum_score :integer +# redflags_count :integer default(0) +# summary :text +# recommendation :text +# stage_id :integer +# current_status :string +# total_score :integer +# maximum_score :integer +# redflags_count :integer default(0) +# published :boolean default(false) +# was_published :boolean default(false) +# published_at :datetime +# +# Indexes +# +# index_project_revisions_on_project_id (project_id) +# index_project_revisions_on_revision_id (revision_id) +# index_project_revisions_on_stage_id (stage_id) +# +# Foreign Keys +# +# fk_rails_... (project_id => projects.id) +# fk_rails_... (revision_id => revisions.id) +# fk_rails_... (stage_id => project_stages.id) +# + +class PhaseRevision < ApplicationRecord + belongs_to :phase + belongs_to :revision + belongs_to :stage, class_name: 'ProjectStage', optional: true + + has_many :ratings, class_name: 'PhaseRevisionRating' + + delegate :version, :tags, to: :revision + + scope :published, -> { where(published: true) } + scope :once_published, -> { where(was_published: true, published: false) } + + ROUTE_MAP = { + 'Prípravná fáza' => 'hodnotenie-pripravy', + 'Fáza produkt' => 'hodnotenie-produktu' + }.freeze + + def load_from_data(raw) + self.title = raw['title'].gsub('Red Flags:', '').strip + + body = raw['post_stream']['posts'].first['cooked'] + summary, rest = body.split(/
#{text_content}
" + end + end + end + + def parse_stars(stars_str) + filled_stars = stars_str.count('★') + grey_stars = stars_str.count('☆') + '' * filled_stars + '' * grey_stars + end + + def parse_inline_object(inline_object) + if inline_object.inline_object_properties + embedded_object = inline_object.inline_object_properties.embedded_object + if embedded_object&.image_properties + " +<%= revision.version %> | -<%= revision.title %> | ++ <% if revision.published? %> + <%= link_to revision.title, project_show_revision_type_path(@project, PhaseRevision.map_phase_type_to_route(@page.phase.phase_type.name)) %> + <% else %> + <%= revision.title %> + <% end %> + | <% if @page.publishable? %> <% if revision.published? %> @@ -81,7 +68,7 @@ latest <% end %> | -<%= l revision.updated_at, format: '%F %T' %> | +<%= l revision.updated_at.in_time_zone('Europe/Bratislava'), format: "%H:%M, %d.%m.%y" %> |
<% if @page.publishable? %>
<% if revision.published? %>
diff --git a/app/views/layouts/no_header_footer.html.erb b/app/views/layouts/no_header_footer.html.erb
new file mode 100644
index 0000000..dcfd207
--- /dev/null
+++ b/app/views/layouts/no_header_footer.html.erb
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+<%= yield %>
+
+
diff --git a/app/views/phase_revision/pdf.html.erb b/app/views/phase_revision/pdf.html.erb
new file mode 100644
index 0000000..373ea63
--- /dev/null
+++ b/app/views/phase_revision/pdf.html.erb
@@ -0,0 +1,97 @@
+<% content_for(:title) do %><%= @phase_revision.title %> · Red Flags · Slovensko.Digital<% end %>
+
+
diff --git a/app/views/phase_revision/show.html.erb b/app/views/phase_revision/show.html.erb
new file mode 100644
index 0000000..74af6cf
--- /dev/null
+++ b/app/views/phase_revision/show.html.erb
@@ -0,0 +1,109 @@
+<% content_for(:title) do %><%= @phase_revision.title %> · Red Flags · Slovensko.Digital<% end %>
+
+
+
+
+
+ <%= @phase_revision.title %>+ <%= link_to 'Hodnotenie na RF webe', project_show_revision_type_path(@phase_revision.phase.project_id, PhaseRevision.map_phase_type_to_route(@phase_revision.phase.phase_type.name))%> + <% @phase_revision.tags.select { |tag| ProjectsHelper::ALLOWED_TAGS[tag] }.each do |tag| %> ++ <%= link_to projects_path(tag: tag) do%> + <%= ProjectsHelper::ALLOWED_TAGS[tag] %> + <% end %> + + <% end %> +<%= @phase_revision.description %> +
+
+
+ <% if @phase_revision.summary.present? %>
+
+
+
+ Náklady na projekt+<%= @phase_revision.budget %> +
+
+ Garant <%= help_icon_if_blank(@phase_revision.guarantor) %>+<%= @phase_revision.guarantor %> +
+
+ <% end %>
+
+ <% if @phase_revision.recommendation.present? %>
+
+
+ Zhrnutie hodnotenia Red Flags+<%= @phase_revision.summary %> +
+
+ <% end %>
+
+
+
+ Stanovisko Slovensko.Digital+<%= @phase_revision.recommendation %> +
+
+
+
+
+ Hodnotenie zverejnené dňa+<%= l(@phase_revision.updated_at.to_date, format: :human) %> + <% if @phase_revision.outdated? %> +
+
+ <% end %>
+ Hodnotenie nie je aktuálne++ V tomto projekte nastali zmeny, ktoré nie sú zahrnuté v súčasnom hodnotení. Na aktualizácii pracujeme, môžete sa do nej + +
+
+
+
+
+ Aktuálny stav projektu+<%= @phase_revision.stage %> +
+
+ Čo sa práve deje+<%= @phase_revision.current_status&.html_safe %> +
+
+
+
+
+
+
+ Hodnotenie<% if @phase_revision.redflags_count > 0 %> <%= @phase_revision.redflags_count %> × <%= fa_icon('flag', class: 'text-danger') %><% end %>++
+ <% @phase_revision.ratings.index_by(&:rating_type).each do |rating_type, rating| %>
+
+
+ <%= rating_stars(rating) %>
+
+ <% end %>
+ <%= rating_type.name %> +
+
+Detailné hodnotenie projektu+
+ <%== formatted_body_html(@phase_revision.body_html) %>
+
+
+
+
diff --git a/app/views/projects/index.html.erb b/app/views/projects/index.html.erb
index 53d4a15..e5e6a09 100644
--- a/app/views/projects/index.html.erb
+++ b/app/views/projects/index.html.erb
@@ -2,44 +2,138 @@
+
+
+
+
+ <%= @phase_revision.title %>+ <% if @phase_revision.published? %> + <%= link_to 'Stiahnuť PDF', project_show_pdf_project_path(@phase_revision.phase.project_id, PhaseRevision.map_phase_type_to_route(@phase_revision.phase.phase_type.name)) %> + <% end %> + <% @phase_revision.tags.select { |tag| ProjectsHelper::ALLOWED_TAGS[tag] }.each do |tag| %> ++ <%= link_to projects_path(tag: tag) do%> + <%= ProjectsHelper::ALLOWED_TAGS[tag] %> + <% end %> + + <% end %> +<%= @phase_revision.description %> +
+
+
+ <% if @phase_revision.summary.present? %>
+
+
+
+ Náklady na projekt+<%= @phase_revision.budget %> +
+
+ Garant <%= help_icon_if_blank(@phase_revision.guarantor) %>+<%= @phase_revision.guarantor %> +
+
+ <% end %>
+
+ <% if @phase_revision.recommendation.present? %>
+
+
+ Zhrnutie hodnotenia Red Flags+<%= @phase_revision.summary %> +
+
+ <% end %>
+
+
+
+ Stanovisko Slovensko.Digital+<%= @phase_revision.recommendation %> +
+
+
+
+
+ Hodnotenie zverejnené dňa+<%= l(@phase_revision.updated_at.to_date, format: :human) %> + <% if @phase_revision.outdated? %> +
+
+ <% end %>
+ Hodnotenie nie je aktuálne++ V tomto projekte nastali zmeny, ktoré nie sú zahrnuté v súčasnom hodnotení. Na aktualizácii pracujeme, môžete sa do nej + +
+
+
+
+
+ Aktuálny stav projektu+<%= @phase_revision.stage %> +
+
+ Čo sa práve deje+<%= @phase_revision.current_status&.html_safe %> +
+
+
+
+
+
+
+ Hodnotenie<% if @phase_revision.redflags_count > 0 %> <%= @phase_revision.redflags_count %> × <%= fa_icon('flag', class: 'text-danger') %><% end %>++
+ <% @phase_revision.ratings.index_by(&:rating_type).each do |rating_type, rating| %>
+
+
+ <%= rating_stars(rating) %>
+
+ <% end %>
+ <%= rating_type.name %> +
+
+
+ <% if @once_published_phase_revisions && @once_published_phase_revisions.any? %>
+ Detailné hodnotenie projektu+
+ <%== formatted_body_html(@phase_revision.body_html) %>
+
+ Predchádzajúce hodnotenia+
Zoznam hodnotených projektovChýba Vám tu nejaký projekt? Nezdá sa Vám hodnotenie? Toto hodnotenie je možné <%= link_to 'dopĺňať a upravovať', contribute_path %>. +
+ <%= form_with url: projects_path, method: :get, local: true, id: "sort-form" do |form| %>
+ <% form.label :sort %>
+ <%= form.select :sort, options_for_select([
+ ['Usporiadať', '', {disabled: true, selected: true}],
+ ['Abecedne (A-Z)', 'alpha'],
+ ['Abecedne (Z-A)', 'alpha_reverse'],
+ ['Podľa dátumu (najnovšie)', 'newest'],
+ ['Podľa dátumu (najstaršie)', 'oldest'],
+ ['Hodnotenie prípravy (vzostupne)', 'preparation_lowest'],
+ ['Hodnotenie prípravy (zostupne)', 'preparation_highest'],
+ ['Hodnotenie produktu (vzostupne)', 'product_lowest'],
+ ['Hodnotenie produktu (zostupne)', 'product_highest'],
+ ], params[:sort]), {}, { class: 'bg-primary px-4 py-2 rounded border-0 text-white', onchange: "this.form.submit();" } %>
+ <% end %>
+
+
|