From 1be60d6bfa8d11550d4571b3725885bb9b3b9b74 Mon Sep 17 00:00:00 2001 From: Eric Pugh Date: Wed, 16 Aug 2023 12:09:30 -0700 Subject: [PATCH] Introduce new Homepage that Summarizes Your Progress (+ new nav?) (#684) Brand new amazing awesome way better than before so needed and with a UX designed by an engineer ;-). --- .rubocop.yml | 4 +- Gemfile | 2 + Gemfile.lock | 9 + .../javascripts/controllers/mainCtrl.js | 4 +- app/assets/javascripts/routes.js | 8 - .../javascripts/services/caseTryNavSvc.js | 20 +- app/assets/stylesheets/_admin2.scss | 6 +- .../analytics/sparkline_controller.rb | 18 ++ app/controllers/core_controller.rb | 12 +- app/controllers/home_controller.rb | 68 +++++++ app/controllers/users/signups_controller.rb | 3 - app/helpers/application_helper.rb | 18 +- app/helpers/home_helper.rb | 62 ++++++ app/models/score.rb | 4 +- .../analytics/sparkline/_event.json.jbuilder | 6 + .../analytics/sparkline/vega_data.csv.erb | 2 + .../sparkline/vega_data.json.jbuilder | 3 + .../sparkline/vega_specification.json.erb | 99 ++++++++++ app/views/books/edit.html.erb | 17 +- app/views/books/index.html.erb | 21 +- app/views/books/new.html.erb | 14 +- app/views/books/show.html.erb | 17 +- app/views/home/_book_summary.html.erb | 20 ++ app/views/home/_case.html.erb | 21 ++ app/views/home/_case_summary.html.erb | 78 ++++++++ app/views/home/_grouped_cases.html.erb | 19 ++ app/views/home/show.html.erb | 181 ++++++++++++++++++ app/views/layouts/_header.html.erb | 2 +- app/views/layouts/_header2.html.erb | 63 ++++++ app/views/layouts/_sidebar.html.erb | 146 ++++++++++++++ app/views/layouts/application.html.erb | 120 ++++++------ config/routes.rb | 8 +- db/sample_data_seeds.rb | 75 ++++++++ .../angular/services/caseTryNavSvc_spec.js | 6 - .../api/v1/users_controller_test.rb | 2 +- test/controllers/home_controller_test.rb | 25 +++ 36 files changed, 1056 insertions(+), 127 deletions(-) create mode 100644 app/controllers/analytics/sparkline_controller.rb create mode 100644 app/controllers/home_controller.rb create mode 100644 app/helpers/home_helper.rb create mode 100644 app/views/analytics/sparkline/_event.json.jbuilder create mode 100644 app/views/analytics/sparkline/vega_data.csv.erb create mode 100644 app/views/analytics/sparkline/vega_data.json.jbuilder create mode 100644 app/views/analytics/sparkline/vega_specification.json.erb create mode 100644 app/views/home/_book_summary.html.erb create mode 100644 app/views/home/_case.html.erb create mode 100644 app/views/home/_case_summary.html.erb create mode 100644 app/views/home/_grouped_cases.html.erb create mode 100644 app/views/home/show.html.erb create mode 100644 app/views/layouts/_header2.html.erb create mode 100644 app/views/layouts/_sidebar.html.erb create mode 100644 test/controllers/home_controller_test.rb diff --git a/.rubocop.yml b/.rubocop.yml index 0f7e8bbae..fae8ffc63 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,4 +1,6 @@ -require: rubocop-rails +require: + - rubocop-rails + - rubocop-capybara AllCops: diff --git a/Gemfile b/Gemfile index 5709f51ad..005c81ce1 100755 --- a/Gemfile +++ b/Gemfile @@ -93,3 +93,5 @@ group :test do gem 'selenium-webdriver' gem 'webdrivers' end + +gem 'prophet-rb', '~> 0.4.2' diff --git a/Gemfile.lock b/Gemfile.lock index 97ebf36f6..2a7eee95a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -106,6 +106,7 @@ GEM regexp_parser (>= 1.5, < 3.0) xpath (~> 3.2) choice (0.2.0) + cmdstan (0.2.2) colorize (0.8.1) concurrent-ruby (1.2.2) connection_pool (2.4.0) @@ -231,6 +232,7 @@ GEM racc (~> 1.4) nokogiri (1.15.2-x86_64-linux) racc (~> 1.4) + numo-narray (0.9.2.1) oauth2 (2.0.9) faraday (>= 0.17.3, < 3.0) jwt (>= 1.0, < 3.0) @@ -267,6 +269,10 @@ GEM postmark-rails (0.22.1) actionmailer (>= 3.0.0) postmark (>= 1.21.3, < 2.0) + prophet-rb (0.4.2) + cmdstan (>= 0.2) + numo-narray (>= 0.9.1.7) + rover-df public_suffix (5.0.1) puma (6.3.0) nio4r (~> 2.0) @@ -335,6 +341,8 @@ GEM actionpack (>= 5.2) railties (>= 5.2) rexml (3.2.5) + rover-df (0.3.4) + numo-narray (>= 0.9.1.9) rubocop (1.54.1) json (~> 2.3) language_server-protocol (>= 3.17.0) @@ -472,6 +480,7 @@ DEPENDENCIES omniauth-keycloak omniauth-rails_csrf_protection (~> 1.0) postmark-rails + prophet-rb (~> 0.4.2) puma pundit rack-cors (~> 2.0) diff --git a/app/assets/javascripts/controllers/mainCtrl.js b/app/assets/javascripts/controllers/mainCtrl.js index 2e1abc963..9a8dd9031 100644 --- a/app/assets/javascripts/controllers/mainCtrl.js +++ b/app/assets/javascripts/controllers/mainCtrl.js @@ -4,13 +4,13 @@ angular.module('QuepidApp') // there's a lot of dependencies here, but this guy // is responsible for bootstrapping everyone so... .controller('MainCtrl', [ - '$scope', '$routeParams', '$location', '$rootScope', '$log', + '$scope', '$routeParams', '$rootScope', '$log', 'flash', 'caseSvc', 'settingsSvc', 'querySnapshotSvc', 'caseTryNavSvc', 'queryViewSvc', 'queriesSvc', 'docCacheSvc', 'diffResultsSvc', 'scorerSvc', 'paneSvc', function ( - $scope, $routeParams, $location, $rootScope, $log, + $scope, $routeParams, $rootScope, $log, flash, caseSvc, settingsSvc, querySnapshotSvc, caseTryNavSvc, queryViewSvc, queriesSvc, docCacheSvc, diffResultsSvc, scorerSvc, diff --git a/app/assets/javascripts/routes.js b/app/assets/javascripts/routes.js index 666194307..974b61e84 100644 --- a/app/assets/javascripts/routes.js +++ b/app/assets/javascripts/routes.js @@ -23,10 +23,6 @@ angular.module('QuepidApp') $locationProvider.html5Mode(true); $routeProvider - .when('/', { - controller: 'BootstrapCtrl', - template: '' - }) .when('/case/:caseNo/try/:tryNo', { templateUrl: 'views/queriesLayout.html', controller: 'MainCtrl', @@ -37,10 +33,6 @@ angular.module('QuepidApp') controller: 'MainCtrl', reloadOnSearch: false }) - .when('/case', { - templateUrl: 'views/cases/index.html', - controller: 'CasesCtrl' - }) .when('/cases', { templateUrl: 'views/cases/index.html', controller: 'CasesCtrl' diff --git a/app/assets/javascripts/services/caseTryNavSvc.js b/app/assets/javascripts/services/caseTryNavSvc.js index d99bf2d59..981474713 100644 --- a/app/assets/javascripts/services/caseTryNavSvc.js +++ b/app/assets/javascripts/services/caseTryNavSvc.js @@ -7,34 +7,18 @@ // What did I do here, like implement a router on top of my router!?!? angular.module('QuepidApp') .service('caseTryNavSvc', [ - '$location', '$timeout', '$window', - function caseTryNavSvc($location, $timeout, $window) { + '$location', '$timeout', + function caseTryNavSvc($location, $timeout) { var caseNo = 0; var tryNo = 0; - var bootstrapPath = null; var currNavDelay = 1000; var isLoading = false; - - this.pathRequested = function(caseTryObj) { - bootstrapPath = caseTryObj; - }; - this.isLoading = function() { return isLoading; }; - - this.bootstrap = function() { - if (bootstrapPath) { - this.navigateTo(bootstrapPath); - bootstrapPath = null; - } else { - $window.location.reload(); - } - }; - this.navigateTo = function(caseTryObj, navDelay) { if (navDelay === undefined) { navDelay = 1000; diff --git a/app/assets/stylesheets/_admin2.scss b/app/assets/stylesheets/_admin2.scss index 553dccd2b..a3ff7be1c 100644 --- a/app/assets/stylesheets/_admin2.scss +++ b/app/assets/stylesheets/_admin2.scss @@ -12,6 +12,6 @@ } // push the Admin Panel down from the top a bit since it's got plenty of room -main > .container { - padding: 60px 15px 0; -} +//main > .container { +// padding: 60px 15px 0; +//} diff --git a/app/controllers/analytics/sparkline_controller.rb b/app/controllers/analytics/sparkline_controller.rb new file mode 100644 index 000000000..4df89fff2 --- /dev/null +++ b/app/controllers/analytics/sparkline_controller.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require 'csv' +module Analytics + class SparklineController < ApplicationController + layout 'analytics' + + def show + end + + def vega_specification + end + + def vega_data + @scores = Score.where(case_id: @current_user.cases.not_archived.select(:id)).includes([ :case ]) + end + end +end diff --git a/app/controllers/core_controller.rb b/app/controllers/core_controller.rb index 51dff2d8e..57f26f624 100755 --- a/app/controllers/core_controller.rb +++ b/app/controllers/core_controller.rb @@ -2,11 +2,17 @@ # This hosts the main Angular 1 application that runs in the client. class CoreController < ApplicationController - before_action :set_case_or_bootstrap - before_action :populate_from_params + before_action :set_case_or_bootstrap, except: :new + before_action :populate_from_params, except: :new def index - # return unless current_user + end + + def new + @case = current_user.cases.build case_name: "Case #{current_user.cases.size}" + @case.save! + + redirect_to case_core_path(@case, @case.tries.first.try_number, params: { showWizard: true }) end private diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb new file mode 100644 index 000000000..2b309a82a --- /dev/null +++ b/app/controllers/home_controller.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +class HomeController < ApplicationController + # rubocop:disable Metrics/AbcSize + def show + # @cases = @current_user.cases.not_archived.includes([ :scores ]) + @cases = @current_user.cases.not_archived + + # copied from dropdown_contoller.rb + @most_recent_cases = lookup_most_recent_cases + + @most_recent_books = [] + @lookup_for_books = {} + @current_user.books_involved_with.order(:updated_at).each do |book| + @most_recent_books << book + judged_by_current_user = book.judgements.where(user: @current_user).count + if judged_by_current_user.positive? && judged_by_current_user < book.query_doc_pairs.count + @lookup_for_books[book] = book.query_doc_pairs.count - judged_by_current_user + + end + break if 4 == @most_recent_books.count + end + + candidate_cases = @cases.select { |kase| kase.scores.scored.count.positive? } + @grouped_cases = candidate_cases.group_by { |kase| kase.case_name.split(':').first } + @grouped_cases = @grouped_cases.select { |_key, value| value.count > 1 } + end + # rubocop:enable Metrics/AbcSize + + private + + # rubocop:disable Metrics/MethodLength + def lookup_most_recent_cases + # Using joins/includes will not return the proper list in the + # correct order because rails refuses to include the + # `case_metadata`.`last_viewed_at` column in the SELECT statement + # which will then cause the ordering not to work properly. + # So instead, we have this beauty! + sql = " + SELECT DISTINCT `cases`.`id`, `case_metadata`.`last_viewed_at` + FROM `cases` + LEFT OUTER JOIN `case_metadata` ON `case_metadata`.`case_id` = `cases`.`id` + LEFT OUTER JOIN `teams_cases` ON `teams_cases`.`case_id` = `cases`.`id` + LEFT OUTER JOIN `teams` ON `teams`.`id` = `teams_cases`.`team_id` + LEFT OUTER JOIN `teams_members` ON `teams_members`.`team_id` = `teams`.`id` + LEFT OUTER JOIN `users` ON `users`.`id` = `teams_members`.`member_id` + WHERE (`teams_members`.`member_id` = #{current_user.id} OR `cases`.`owner_id` = #{current_user.id}) + AND (`cases`.`archived` = false OR `cases`.`archived` IS NULL) + ORDER BY `case_metadata`.`last_viewed_at` DESC, `cases`.`id` DESC + LIMIT 4 + " + + results = ActiveRecord::Base.connection.execute(sql) + + case_ids = [] + results.each do |row| + case_ids << row.first.to_i + end + + # map to objects + most_recent_cases = Case.includes([ :scorer, :scores ]).where(id: [ case_ids ]) + most_recent_cases = most_recent_cases.select { |kase| kase.last_score.present? } + # rubocop:enable + most_recent_cases = most_recent_cases.sort_by(&:case_name) + most_recent_cases + end + # rubocop:enable Metrics/MethodLength +end diff --git a/app/controllers/users/signups_controller.rb b/app/controllers/users/signups_controller.rb index 639242832..72ce2ed8e 100644 --- a/app/controllers/users/signups_controller.rb +++ b/app/controllers/users/signups_controller.rb @@ -17,9 +17,6 @@ def create @user.assign_attributes(user_params_to_save) else @user = User.new user_params_to_save - # in this flow, we have a new user joining, so we create a empty case for them, which - # on the core_controller.rb triggers the bootstrap and the new case wizard. - @user.cases.build case_name: "Case #{@user.cases.size}" end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 284c28a45..98e54b219 100755 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -1,6 +1,14 @@ # frozen_string_literal: true module ApplicationHelper + def make_active? options + if options.key?(:path) + request.fullpath.include?(options[:path]) + elsif options.key?(:controller) + controller_name == options[:controller] + end + end + def bootstrap_class_for flash_type { success: 'alert-success', @@ -85,14 +93,4 @@ def document_fields_parses_as_json document_fields document_fields end - - def document_fields_except_title document_fields - document_fields_json = JSON.parse(document_fields) - special_field_name = 'title' - if special_field_name in document_fields_json - document_fields_json.except(special_field_name) - else - document_fields_json - end - end end diff --git a/app/helpers/home_helper.rb b/app/helpers/home_helper.rb new file mode 100644 index 000000000..e864ce326 --- /dev/null +++ b/app/helpers/home_helper.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +module HomeHelper + def greeting + greetings = [ + 'Good Day', + 'Hello', + "How's your day", + "How's your day going", + 'Good to see you', + 'So good to see you', + 'Hiya!', + 'Bonjour', + 'Hola!' + ] + greetings.sample + end + + # rubocop:disable Metrics/AbcSize + # rubocop:disable Metrics/MethodLength + def greeting2 + current_time = DateTime.current.seconds_since_midnight + midnight = DateTime.now.beginning_of_day.seconds_since_midnight + noon = DateTime.now.middle_of_day.seconds_since_midnight + five_pm = DateTime.now.change(:hour => 17 ).seconds_since_midnight + eight_pm = DateTime.now.change(:hour => 20 ).seconds_since_midnight + + puts "DateTime.current #{DateTime.current}" + puts "midnight: #{midnight}" + puts "noon: #{noon}" + puts "current_time: #{current_time}" + + if midnight.upto(noon).include?(current_time) + greeting = 'Good Morning' + elsif noon.upto(five_pm).include?(current_time) + greeting = 'Good Afternoon' + elsif five_pm.upto(eight_pm).include?(current_time) + greeting = 'Good Evening' + elsif eight_pm.upto(midnight + 1.day).include?(current_time) + greeting = 'Good Night' + end + greeting + end + # rubocop:enable Metrics/AbcSize + # rubocop:enable Metrics/MethodLength + + def book_title book + if book.name.downcase.starts_with?('book') + book.name + else + "Book #{book.name}" + end + end + + def case_title kase + if kase.case_name.downcase.starts_with?('case') + kase.case_name + else + "Case #{kase.case_name}" + end + end +end diff --git a/app/models/score.rb b/app/models/score.rb index 82458a39f..03fcd5aca 100644 --- a/app/models/score.rb +++ b/app/models/score.rb @@ -44,10 +44,12 @@ class Score < ApplicationRecord # Scores scope :last_one, -> { where(annotation_id: nil) - .order(updated_at: :desc) + .order(updated_at: :desc) .order(created_at: :desc) .order(id: :desc) .limit(1) .first } + + scope :scored, -> { where('score > ?', 0) } end diff --git a/app/views/analytics/sparkline/_event.json.jbuilder b/app/views/analytics/sparkline/_event.json.jbuilder new file mode 100644 index 000000000..9d801c36b --- /dev/null +++ b/app/views/analytics/sparkline/_event.json.jbuilder @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +json.case_id score.case_id +json.date score.created_at +json.y score.score +json.symbol score.case.case_name diff --git a/app/views/analytics/sparkline/vega_data.csv.erb b/app/views/analytics/sparkline/vega_data.csv.erb new file mode 100644 index 000000000..087cd96ad --- /dev/null +++ b/app/views/analytics/sparkline/vega_data.csv.erb @@ -0,0 +1,2 @@ +"date","state","positive","positiveIncrease" +<% @scores.each do |score| %><%= ::CSV.generate_line([score.created_at.to_date.to_fs(:number), score.case.case_name, 100, (score.score * 100).to_i]).html_safe %><% end %> diff --git a/app/views/analytics/sparkline/vega_data.json.jbuilder b/app/views/analytics/sparkline/vega_data.json.jbuilder new file mode 100644 index 000000000..ea35a2b19 --- /dev/null +++ b/app/views/analytics/sparkline/vega_data.json.jbuilder @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +json.array! @scores, partial: 'event', as: :score diff --git a/app/views/analytics/sparkline/vega_specification.json.erb b/app/views/analytics/sparkline/vega_specification.json.erb new file mode 100644 index 000000000..5001ab12c --- /dev/null +++ b/app/views/analytics/sparkline/vega_specification.json.erb @@ -0,0 +1,99 @@ +{ + "$schema": "https://vega.github.io/schema/vega-lite/v5.json", + "data": { + "url": "<%=link_to analytics_tries_visualization_vega_data_path, format: csv %> ", + "name": "dataset", + "format": {"type": "csv"} + }, + "facet": { + "row": { + "field": "state", + "sort": { + "field": "positiveIncrease", + "op": "average", + "order": "descending" + } + } + }, + "spec": { + "transform": [ + { + "calculate": "datetime(slice(datum.date,0,4), slice(datum.date,4,6),[slice(datum.date,6,8)])", + "as": "__date" + } + ], + "height": 20, + "layer": [ + { + "mark": {"type": "area"}, + "encoding": { + "x": { + "field": "__date", + "type": "temporal", + "timeUnit": "yearmonthdate", + "axis": { + "title": null, + "orient": "top", + "domain": false, + "ticks": false, + "labels": false, + "grid": false + } + }, + "y": { + "field": "positiveIncrease", + "aggregate": "sum", + "type": "quantitative", + "axis": { + "title": null, + "domain": false, + "labels": false, + "ticks": false, + "grid": false + } + } + } + }, + { + "mark": {"type": "text", "align": "right", "dx": -120}, + "encoding": { + "text": { + "aggregate": {"argmax": "__date"}, + "field": "positiveIncrease", + "format": ",.0f", + "type": "quantitative" + } + } + }, + { + "mark": {"type": "text", "align": "right", "dx": -180}, + "encoding": { + "text": { + "aggregate": {"argmax": "__date"}, + "field": "positive", + "format": ",.0f", + "type": "quantitative" + } + } + } + ] + }, + "resolve": {"scale": {"y": "independent"}}, + "config": { + "view": {"stroke": "transparent"}, + "header": { + "title": null, + "labelAngle": 0, + "labelAlign": "left", + "labelFontSize": 12, + "labelFont": "Arial" + }, + "text": {"font": "Arial", "fontSize": 12}, + "facet": {"spacing": 1}, + "area": { + "interpolate": "monotone", + "line": {"color": "red", "strokeWidth": 1, "interpolate": "monotone"}, + "fill": "#faa" + } + } +} diff --git a/app/views/books/edit.html.erb b/app/views/books/edit.html.erb index 18856ed2b..b091d71ed 100644 --- a/app/views/books/edit.html.erb +++ b/app/views/books/edit.html.erb @@ -1,4 +1,17 @@ -

Editing Book <%= @book.name %>

+
+
+

Edit Book <%= @book.name %>

+
+
+ <%= link_to 'New Book', new_book_path, class: "btn btn-sm btn-outline-secondary" %> + +
+ +
+
<%= render 'form', book: @book %> @@ -10,4 +23,4 @@ <%= button_to 'Delete Book', @book, method: :delete %> -
+
diff --git a/app/views/books/index.html.erb b/app/views/books/index.html.erb index bab7219b6..01f65bcfc 100644 --- a/app/views/books/index.html.erb +++ b/app/views/books/index.html.erb @@ -1,4 +1,17 @@ -

Books

+
+

Books

+
+
+ <%= link_to 'New Book', new_book_path, class: "btn btn-sm btn-outline-secondary" %> + +
+ +
+
+

<%= notice %>

@@ -8,7 +21,7 @@ <% end %> - +
@@ -33,7 +46,5 @@
-
-<%= button_to 'New Book', new_book_path, method: :get %> -
+ diff --git a/app/views/books/new.html.erb b/app/views/books/new.html.erb index da2d55de7..27fb395e2 100644 --- a/app/views/books/new.html.erb +++ b/app/views/books/new.html.erb @@ -1,4 +1,16 @@ -

New Book

+
+

New Book

+
+
+ <%= link_to 'New Book', new_book_path, class: "btn btn-sm btn-outline-secondary" %> + +
+ +
+
<%= render 'form', book: @book %>
diff --git a/app/views/books/show.html.erb b/app/views/books/show.html.erb index e6f9ed5ab..8dbfba452 100644 --- a/app/views/books/show.html.erb +++ b/app/views/books/show.html.erb @@ -1,4 +1,17 @@ -

Book <%= @book.name %>

+
+

Book <%= @book.name %>

+
+
+ <%= link_to 'New Book', new_book_path, class: "btn btn-sm btn-outline-secondary" %> + +
+ +
+
+ Books organize all the query/doc pairs that are needed for evaluating your search queries. <%= render 'judgements/moar_judgements_needed', book: @book %> @@ -65,7 +78,7 @@ To reference these judgements from a notebook or another system you can referenc
Leaderboard
Who is closest to having all query/doc pairs judged
- + <%= Vega.lite .data(@leaderboard_data) diff --git a/app/views/home/_book_summary.html.erb b/app/views/home/_book_summary.html.erb new file mode 100644 index 000000000..4e7dcd5d9 --- /dev/null +++ b/app/views/home/_book_summary.html.erb @@ -0,0 +1,20 @@ +
+
+
+ <%= link_to book_title(book), book_path(book) %> +
+ +
+
+ <%= book.query_doc_pairs.count %> Query/Doc Pairs +
+

<%= book.judgements.count %> Judgements +

+ + <% if @lookup_for_books[book] && @lookup_for_books[book] > 0 %> +

You need to judge <%= @lookup_for_books[book] %> out <%= book.query_doc_pairs.count %> query/doc pairs.

+ <%= link_to 'Judge', book_judge_path(book), class: 'btn btn-sm btn-primary', role: 'button' %> + <% end %> +
+
+
diff --git a/app/views/home/_case.html.erb b/app/views/home/_case.html.erb new file mode 100644 index 000000000..c5903a8f5 --- /dev/null +++ b/app/views/home/_case.html.erb @@ -0,0 +1,21 @@ + + <%= kase.case_name %> + <%= kase.queries.count %> + + <% unless kase.last_score.blank? %> + <%= kase.last_score.score %> + <% end %> + + + <% unless kase.last_score.blank? %> + <%= kase.last_score.created_at%> + <% end %> + + + <% unless kase.last_score.blank? %> + <%= kase.last_score.user.name%> + <% end %> + + + <%= link_to 'View', case_core_path(kase), class: 'btn btn-sm btn-primary', role: 'button' %> + diff --git a/app/views/home/_case_summary.html.erb b/app/views/home/_case_summary.html.erb new file mode 100644 index 000000000..e206002c2 --- /dev/null +++ b/app/views/home/_case_summary.html.erb @@ -0,0 +1,78 @@ +
+
+
+ <%= link_to case_title(kase), case_core_path(kase) %> +
+ +
+
<%= kase.last_score.score %> <%= kase.scorer.name %>
+

<%= kase.created_at.to_date.to_fs(:short) %> - <%= kase.last_score.updated_at.to_date.to_fs(:short) %> +

+ + <% + #df = Rover.read_csv("example.csv") + data = kase.scores.collect{ |score| {ds: score.created_at.to_date.to_fs(:db), y: score.score, datetime: score.created_at.to_date } }.uniq + # warning! blunt filter below! + data = data.uniq { |h| h[:ds] } + data = data.map {|h| h.transform_keys(&:to_s) } + + do_changepoints = data.length >= 3 ? true : false # need at least 3... + + if do_changepoints + df = Rover::DataFrame.new(data) + m = Prophet.new() + m.fit(df) + %> + + <% if change > 0 %> + <% if change.positive? %> +

<%= number_to_percentage(change, precision:0) %> increase since <%=time_ago_in_words(last_changepoint) %> ago

+ <% else %> +

<%= number_to_percentage(change, precision:0) %> decrease since <%=time_ago_in_words(last_changepoint) %> ago

+ <% end %> + <% end %> + <% end # if do_changepoint %> + + <% + # look at https://github.com/ankane/prophet-ruby to remove outliers. + data = kase.scores.collect{ |score| {x: score.created_at.to_date.to_fs(:db), y: score.score } }.uniq + # warning! blunt filter below! + data = data.uniq { |h| h[:x] } + %> + + + + + <%= Vega.lite + .data(data) + .mark(type: "line", tooltip: true, interpolate: "cardinal", point: {size: 60}) + .encoding( + x: {field: "x", type: "temporal", scale: {type: "utc"}, axis: {format: "%b %e"}}, + y: {field: "y", type: "quantitative"} + ) + .height(60) + .config(axis: {title: nil, labelFontSize: 12}) %> +
+
+
diff --git a/app/views/home/_grouped_cases.html.erb b/app/views/home/_grouped_cases.html.erb new file mode 100644 index 000000000..de77002bd --- /dev/null +++ b/app/views/home/_grouped_cases.html.erb @@ -0,0 +1,19 @@ +
+
Results for <%=grouped_cases_name %> Cases
+
+ + <% grouped_cases_data = grouped_cases.each.collect{|kase| kase.scores.collect{ |score| {x: score.created_at.to_date.to_fs(:db), y: score.score, z: score.case.case_name } }}.flatten.uniq + %> + + <%= Vega.lite + .data(grouped_cases_data) + .mark(type: "line", point: {size: 60}, tooltip: true, interpolate: "cardinal") + .encoding( + x: {field: "x", type: "temporal", scale: {type: "utc"}, axis: {format: "%b %e"}}, + y: {field: "y", type: "quantitative"}, + color: {field: "z", axis: {title: nil}} + ) + .config(axis: {title: nil, labelFontSize: 12}) %> + +
+
diff --git a/app/views/home/show.html.erb b/app/views/home/show.html.erb new file mode 100644 index 000000000..1ae274936 --- /dev/null +++ b/app/views/home/show.html.erb @@ -0,0 +1,181 @@ + + + +
+

+

<%= Time.now.strftime("%A, %B %d") %>
+

<%= greeting() %>, <%= @current_user.name.titleize %>

+
+ +
+ <%= render partial: "case_summary", collection: @most_recent_cases, as: :kase %> +
+ +
+ <%= render partial: "book_summary", collection: @most_recent_books, as: :book %> +
+ +<% if @cases.empty? %> +
+
+

Welcome to Quepid!

+

+ We're so excited that you are embarking on the journey to improving search quality! There is so much to explore in Quepid. However a good place to start is to create your first relevancy case using our demo datasets. +

+
+ <%= button_to "Create Your First Relevancy Case", case_new_path, method: :get, class: 'd-inline-flex align-items-center btn btn-primary btn-lg px-4 rounded-pill' %> + +
+
+
+<% else %> +
+
+
+
Cases
+
+
+ + + + + + + + + + + + + <%= render partial: "case", collection: @cases, as: :kase %> + +
Case Title# of QueriesLast ScoreLast Run OnLast Run By
+
+ <%= link_to 'View all', cases_path(), class: 'btn btn-block btn-light', role: 'button' %> + +
+
+
+
+ + <% @grouped_cases.keys[0..2].each do |key| %> + <%= render partial: "grouped_cases", locals: {grouped_cases_name: key, grouped_cases: @grouped_cases[key]}%> + <% end %> + +
+
+<% end %> + +
+
+ + diff --git a/app/views/layouts/_header.html.erb b/app/views/layouts/_header.html.erb index 0aa3d308d..93b4294fe 100644 --- a/app/views/layouts/_header.html.erb +++ b/app/views/layouts/_header.html.erb @@ -38,7 +38,7 @@
  • <%= link_to 'My profile', profile_path, class: 'dropdown-item', target: '_self' %>
  • <%= link_to 'Log out', logout_path, class: 'dropdown-item', target: '_self' %>
  • -
  • <%= link_to 'Judgements', books_path, class: 'dropdown-item', target: '_self' %>
  • +
  • <%= link_to 'Judgements', books_path, class: 'dropdown-item', target: '_self' %>
  • <% if current_user.administrator? %>
  • <%= link_to 'Admin Home', admin_path, class: 'dropdown-item', target: '_self' %>
  • diff --git a/app/views/layouts/_header2.html.erb b/app/views/layouts/_header2.html.erb new file mode 100644 index 000000000..29d8c743e --- /dev/null +++ b/app/views/layouts/_header2.html.erb @@ -0,0 +1,63 @@ + diff --git a/app/views/layouts/_sidebar.html.erb b/app/views/layouts/_sidebar.html.erb new file mode 100644 index 000000000..a5d210701 --- /dev/null +++ b/app/views/layouts/_sidebar.html.erb @@ -0,0 +1,146 @@ + + + + + + + + diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index f43d709cb..09abb63a2 100755 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -7,75 +7,79 @@ <% if flash[:unfurl] %> - - -Quepid: Case <%= flash[:unfurl]["case_name"] %> - - - -<% if flash[:unfurl]["id"] %> -" /> -<% end %> -" /> - Description here." /> -" /> - - - - - -" /> - Description here" /> -" /> -<% if flash[:unfurl]["id"] %> -" /> - -" /> -<% end %> + + + Quepid: Case <%= flash[:unfurl]["case_name"] %> + + + + <% if flash[:unfurl]["id"] %> + " /> + <% end %> + " /> + Description here." /> + " /> + + + + + + " /> + Description here" /> + " /> + <% if flash[:unfurl]["id"] %> + " /> + + " /> + <% end %> <% else %> - - -Quepid -- Relevant Search Solved - - - - - - - -" /> - - - - - - -" /> - + + + Quepid -- Relevant Search Solved + + + + + + + + " /> + + + + + + + " /> + <% end %> - + <%= stylesheet_link_tag 'application', media: 'all' %> <%= csrf_meta_tags %> <%= javascript_include_tag 'application' %> - - -
    - <%= render 'layouts/header' %> -
    + - -
    -
    - <%= yield %> -
    -
    + - <%= render 'layouts/footer' %> +<%= render 'layouts/header' %> - <%= render 'layouts/common_js' %> +
    +
    + + <% if @current_user && !(controller_name == 'judgements' and ['new','edit'].any? params[:action]) %> + <%= render 'layouts/sidebar' %> + <% end %> + + +
    + <%= yield %> +
    +
    +
    + <%= render 'layouts/common_js' %> diff --git a/config/routes.rb b/config/routes.rb index aebe6df21..e5cd52a8d 100755 --- a/config/routes.rb +++ b/config/routes.rb @@ -4,6 +4,8 @@ # rubocop:disable Metrics/BlockLength Rails.application.routes.draw do + # get 'home/show' + root 'home#show' # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html Healthcheck.routes(self) @@ -58,6 +60,9 @@ resources :cases do resource :visibility, only: [ :update ], module: :cases end + get 'sparkline/vega_specification' => 'sparkline#vega_specification', + as: :sparkline_vega_specification + get 'sparkline/vega_data' => 'sparkline#vega_data', as: :sparkline_vega_data end namespace :admin do @@ -69,8 +74,6 @@ resources :communal_scorers end - root 'core#index' - # preview routes for mailers if Rails.env.development? get '/rails/mailers' => 'rails/mailers#index' @@ -193,6 +196,7 @@ # Routes handled by angular get '/case/:id(/try/:try_number)' => 'core#index', as: :case_core + get '/cases/new' => 'core#new', as: :case_new get '/cases' => 'core#index' get '/case' => 'core#index' get '/cases/import' => 'core#index' diff --git a/db/sample_data_seeds.rb b/db/sample_data_seeds.rb index 1f898dd51..9cc5cf34b 100755 --- a/db/sample_data_seeds.rb +++ b/db/sample_data_seeds.rb @@ -281,12 +281,24 @@ def print_case_info the_case new_try = tens_of_queries_case.tries.build try_params + try_number = tens_of_queries_case.last_try_number + 1 new_try.try_number = try_number tens_of_queries_case.last_try_number = try_number new_try.save + + score_specifics = { + user: realistic_activity_user, + try: new_try, + score: (0.01 * counter), + created_at: DateTime.now - (30 - counter).days, + updated_at: DateTime.now - (30 - counter).days + } + new_case_score = tens_of_queries_case.scores.build (score_specifics) + new_case_score.save + tens_of_queries_case.save # seventy percent of the time lets grab a new origin for the try in the tree @@ -331,9 +343,72 @@ def print_case_info the_case query_doc_pair.judgements << Judgement.new(rating: rating.rating, user: osc_member_user) query_doc_pair.save end + + book.reload + book.query_doc_pairs.shuffle[0..2].each do |query_doc_pair| + query_doc_pair.judgements << Judgement.new(rating: query_doc_pair.judgements.first.rating, user: realistic_activity_user) + query_doc_pair.save + end + +end + +# Mulitple Cases +print_step "Seeding Multiple cases................" +case_names = ["Typeahead: Dairy", "Typeahead: Meats", "Typeahead: Dessert", "Typeahead: Fruit & Veg"] + +case_names.each do |case_name| + + kase = realistic_activity_user.cases.create case_name: case_name + + days_of_experimentation = rand(3..20) # somewhere between + + days_of_experimentation.times do |counter| + + try_specifics = { + try_number: counter, + query_params: 'q=#$query##&magicBoost=' + (counter+2).to_s + } + + try_params = try_defaults.merge(try_specifics) + + new_try = kase.tries.build try_params + + + try_number = kase.last_try_number + 1 + + new_try.try_number = try_number + kase.last_try_number = try_number + + new_try.save + + score_specifics = { + user: realistic_activity_user, + try: new_try, + score: (0.01 * counter), + created_at: DateTime.now - (days_of_experimentation - counter).days, + updated_at: DateTime.now - (days_of_experimentation - counter).days + } + new_case_score = kase.scores.build (score_specifics) + new_case_score.save + + kase.save + + # seventy percent of the time lets grab a new origin for the try in the tree + # 30 percent of the time we just add a new one + if rand(0..100) <= 70 + parent_try = kase.tries.sample + end + new_try.parent = parent_try + + new_try.save + + + end + end +print_step "End of Multiple cases................" # Big Cases diff --git a/spec/javascripts/angular/services/caseTryNavSvc_spec.js b/spec/javascripts/angular/services/caseTryNavSvc_spec.js index 4fe68936e..561fa40c1 100644 --- a/spec/javascripts/angular/services/caseTryNavSvc_spec.js +++ b/spec/javascripts/angular/services/caseTryNavSvc_spec.js @@ -63,12 +63,6 @@ describe('Service: caseTryNavSvc', function () { expect(locationMock.path).toHaveBeenCalledWith('/case/5/try/2/'); }); - it('bootstraps', function() { - caseTryNavSvc.pathRequested({caseNo: 1, tryNo: 2}); - caseTryNavSvc.bootstrap(); - expect(locationMock.path).toHaveBeenCalledWith('/case/1/try/2/'); - }); - it('doesnt save nav till confirmed', function() { caseTryNavSvc.navigateTo({caseNo: 5, tryNo: 1}); expect(locationMock.path).toHaveBeenCalledWith('/case/5/try/1/'); diff --git a/test/controllers/api/v1/users_controller_test.rb b/test/controllers/api/v1/users_controller_test.rb index 59f19e9a9..9e71c831c 100644 --- a/test/controllers/api/v1/users_controller_test.rb +++ b/test/controllers/api/v1/users_controller_test.rb @@ -89,7 +89,7 @@ class UsersControllerTest < ActionController::TestCase body = response.parsed_body # rubocop:disable Layout/LineLength - assert body['default_scorer_id'].include? "Does not exist" + assert body['default_scorer_id'].include? 'Does not exist' # assert body['default_scorer_id'].include? I18n.t('activerecord.errors.models.user.attributes.default_scorer_id.existence') # rubocop:enable Layout/LineLength diff --git a/test/controllers/home_controller_test.rb b/test/controllers/home_controller_test.rb new file mode 100644 index 000000000..d93cb997f --- /dev/null +++ b/test/controllers/home_controller_test.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +require 'test_helper' + +class HomeControllerTest < ActionDispatch::IntegrationTest + test 'should get redirected to log in' do + get root_url + assert_response :redirect + end + + test 'can I group things' do + case_names = [ 'Typeahead: Dairy', 'Typeahead: Meats', 'Typeahead: Dessert', 'Typeahead: Fruit & Veg', + 'Global Search', 'Nested:Search:IsFun' ] + + grouped_names = case_names.group_by { |name| name.split(':').first } + + puts grouped_names + assert_not_nil grouped_names['Typeahead'] + assert_equal grouped_names['Typeahead'].count, 4 + assert_not_nil grouped_names['Global Search'] + assert_equal grouped_names['Global Search'].count, 1 + assert_not_nil grouped_names['Nested'] + assert_equal grouped_names['Nested'].count, 1 + end +end