From 858b33d1ae274307a7d37605243a47b68337d836 Mon Sep 17 00:00:00 2001 From: Evil Potato Date: Tue, 4 Jul 2023 18:24:18 +0300 Subject: [PATCH 1/6] refactor: remove excess check in AchievementService --- app/services/AchievementService.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/AchievementService.rb b/app/services/AchievementService.rb index 0ee824a..6b3f634 100644 --- a/app/services/AchievementService.rb +++ b/app/services/AchievementService.rb @@ -18,7 +18,7 @@ def reward(badge) end def first_try?(_) - @user.test_passages.where(test_id: @test.id, finished: true).count == 1 + @user.test_passages.where(test_id: @test.id).count == 1 end def category_complete?(category) From cdfb0a3d3c144babbe85e83d3a02c8af4186a883 Mon Sep 17 00:00:00 2001 From: Evil Potato Date: Wed, 5 Jul 2023 01:35:02 +0300 Subject: [PATCH 2/6] feat: migration add duration time in the table tests --- db/migrate/20230704222539_add_timer_to_tests.rb | 5 +++++ db/schema.rb | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20230704222539_add_timer_to_tests.rb diff --git a/db/migrate/20230704222539_add_timer_to_tests.rb b/db/migrate/20230704222539_add_timer_to_tests.rb new file mode 100644 index 0000000..3f31743 --- /dev/null +++ b/db/migrate/20230704222539_add_timer_to_tests.rb @@ -0,0 +1,5 @@ +class AddTimerToTests < ActiveRecord::Migration[6.1] + def change + add_column :tests, :duration_time, :integer + end +end diff --git a/db/schema.rb b/db/schema.rb index 0b748b8..8e0d797 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2023_06_29_235442) do +ActiveRecord::Schema.define(version: 2023_07_04_222539) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -93,6 +93,7 @@ t.datetime "updated_at", precision: 6, null: false t.bigint "author_id" t.boolean "active", default: false + t.integer "duration_time" t.index ["author_id"], name: "index_tests_on_author_id" t.index ["category_id"], name: "index_tests_on_category_id" t.index ["title", "level"], name: "index_tests_on_title_and_level", unique: true From bf346bf89fe7de8edf3015ae2dc9d11296134e17 Mon Sep 17 00:00:00 2001 From: Evil Potato Date: Wed, 5 Jul 2023 02:23:04 +0300 Subject: [PATCH 3/6] feat: add function setup timer in test --- app/controllers/admin/tests_controller.rb | 2 +- app/views/admin/tests/_form.html.erb | 4 ++++ app/views/admin/tests/_test.html.erb | 1 + app/views/admin/tests/index.html.erb | 1 + app/views/tests/_test.html.erb | 1 + app/views/tests/index.html.erb | 1 + config/locales/activerecord.en.yml | 1 + config/locales/activerecord.ru.yml | 1 + config/locales/en.yml | 3 +++ config/locales/ru.yml | 3 +++ 10 files changed, 17 insertions(+), 1 deletion(-) diff --git a/app/controllers/admin/tests_controller.rb b/app/controllers/admin/tests_controller.rb index 2d191f6..4a14408 100644 --- a/app/controllers/admin/tests_controller.rb +++ b/app/controllers/admin/tests_controller.rb @@ -42,7 +42,7 @@ def destroy private def test_params - params.require(:test).permit(:title, :level, :category_id, :active) + params.require(:test).permit(:title, :level, :category_id, :active, :duration_time) end def find_test diff --git a/app/views/admin/tests/_form.html.erb b/app/views/admin/tests/_form.html.erb index 1d1eece..d651262 100644 --- a/app/views/admin/tests/_form.html.erb +++ b/app/views/admin/tests/_form.html.erb @@ -11,6 +11,10 @@ <%= form.label :level %> <%= form.number_field :level, id: :test_level, class: 'form-control' %>

+

+ <%= form.label :duration_time %> + <%= form.number_field :duration_time, id: :test_duration_time, class: 'form-control' %> +

<%= form.label :category_id %> <%= form.collection_select :category_id, Category.all, :id, :title, prompt: true, class: 'dropdown-menu' %> diff --git a/app/views/admin/tests/_test.html.erb b/app/views/admin/tests/_test.html.erb index 8bde97e..8f64a50 100644 --- a/app/views/admin/tests/_test.html.erb +++ b/app/views/admin/tests/_test.html.erb @@ -5,6 +5,7 @@ <%= render 'form_inline', test: test == @test ? @test : test %> <%= test.level %> + <%= test.duration_time %> <%= link_to test.questions.count, admin_test_questions_path(test.id) %> <%= test.active %> diff --git a/app/views/admin/tests/index.html.erb b/app/views/admin/tests/index.html.erb index 366f7ee..3fc7fdf 100644 --- a/app/views/admin/tests/index.html.erb +++ b/app/views/admin/tests/index.html.erb @@ -7,6 +7,7 @@ <%= t('.id') %> <%= t('.name') %> <%= t('.level') %> + <%= t('.duration_time') %> <%= t('.count_question') %> <%= t('.active') %> <%= t('.action') %> diff --git a/app/views/tests/_test.html.erb b/app/views/tests/_test.html.erb index f981457..235c11f 100644 --- a/app/views/tests/_test.html.erb +++ b/app/views/tests/_test.html.erb @@ -2,6 +2,7 @@ <%= test.id %> <%= test.title %> <%= test.level %> + <%= test.duration_time %> <%= test.questions.count %> <%= button_to t('.start'), start_test_path(test), class: 'btn btn-primary' %> diff --git a/app/views/tests/index.html.erb b/app/views/tests/index.html.erb index 01d1ca5..1e2cd0a 100644 --- a/app/views/tests/index.html.erb +++ b/app/views/tests/index.html.erb @@ -10,6 +10,7 @@ <%= octicon 'arrow-down', class: 'text-success hide' %> <%= t('.level')%> + <%= t('.duration_time')%> <%= t('.count_question')%> <%= t('.start')%> diff --git a/config/locales/activerecord.en.yml b/config/locales/activerecord.en.yml index 0cde948..07edfe0 100644 --- a/config/locales/activerecord.en.yml +++ b/config/locales/activerecord.en.yml @@ -18,6 +18,7 @@ en: level: 'Level' category: 'Category' active: 'Active' + duration_time: 'Duration time' test_passage: correct_questions: 'Correct questions' feedback: diff --git a/config/locales/activerecord.ru.yml b/config/locales/activerecord.ru.yml index d7b1951..b15f343 100644 --- a/config/locales/activerecord.ru.yml +++ b/config/locales/activerecord.ru.yml @@ -18,6 +18,7 @@ ru: level: 'Сложность' category: 'Категория' active: 'Активный' + duration_time: 'Время продолжительности' test_passage: correct_questions: 'Правильные ответы' feedback: diff --git a/config/locales/en.yml b/config/locales/en.yml index a6dec42..88bf066 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -60,6 +60,7 @@ en: level: 'Level' category_id: 'Category' active: 'Active' + duration_time: 'Duration time (minutes)' feedback: title: 'Topic' body: 'Feedback' @@ -107,6 +108,7 @@ en: header: 'Test list:' gists: 'Show Gists' badges: 'Show Badges' + duration_time: 'Duration time (minutes)' show: id: 'Id' question: 'Question' @@ -140,6 +142,7 @@ en: start: 'Start' header: 'List of available tests:' my_badges: 'Show my Badges' + duration_time: 'Duration time (minutes)' test: start: 'Start' diff --git a/config/locales/ru.yml b/config/locales/ru.yml index de88c09..545f781 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -28,6 +28,7 @@ ru: level: 'Сложность' category_id: 'Категория' active: 'Активный' + duration_time: 'Время продолжительности (минут)' feedback: title: 'Тема отзыва' body: 'Обратная связь' @@ -75,6 +76,7 @@ ru: header: 'Список тестов:' gists: 'Посмотреть Гисты' badges: 'Посмотреть Бейджи' + duration_time: 'Время продолжительности (минут)' show: id: 'Номер' question: 'Вопрос' @@ -108,6 +110,7 @@ ru: start: 'Пройти тест' header: 'Список доступных тестов:' my_badges: 'Показать мои Бейджи' + duration_time: 'Время продолжительности (минут)' test: start: 'Начать' From 85a3d1a7fb3e7b313c1e70e2e970f62130160df5 Mon Sep 17 00:00:00 2001 From: Evil Potato Date: Wed, 5 Jul 2023 03:26:08 +0300 Subject: [PATCH 4/6] feat: add a test duration timer to the session --- app/controllers/test_passages_controller.rb | 16 +++++++++++++++- app/controllers/tests_controller.rb | 1 + 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/app/controllers/test_passages_controller.rb b/app/controllers/test_passages_controller.rb index 08f20d6..64b8418 100644 --- a/app/controllers/test_passages_controller.rb +++ b/app/controllers/test_passages_controller.rb @@ -1,9 +1,13 @@ class TestPassagesController < ApplicationController before_action :find_test_passage, only: %i[show result update gist] + before_action :get_end_time, only: %i[update result] + before_action :check_end_time, only: %i[update] def show; end - def result; end + def result + session[:"end_time_#{@test_passage.id}"] = nil + end def update @test_passage.accept!(params[:answer_ids]) @@ -41,6 +45,16 @@ def gist private + def get_end_time + @end_time = session[:"end_time_#{@test_passage.id}"] + end + + def check_end_time + if @end_time.present? && @end_time <= Time.now + redirect_to result_test_passage_path(@test_passage) + end + end + def find_test_passage @test_passage = TestPassage.find(params[:id]) end diff --git a/app/controllers/tests_controller.rb b/app/controllers/tests_controller.rb index 2e2b2a5..5de8715 100644 --- a/app/controllers/tests_controller.rb +++ b/app/controllers/tests_controller.rb @@ -7,5 +7,6 @@ def start @test = Test.find(params[:id]) current_user.tests.push(@test) redirect_to current_user.test_passage(@test) + session[:"end_time_#{TestPassage.last.id}"] = Time.now + @test.duration_time.minute if @test.duration_time.present? end end From 29485240f96996ca9f4268f1e1246a19de8c488a Mon Sep 17 00:00:00 2001 From: Evil Potato Date: Thu, 6 Jul 2023 03:44:48 +0300 Subject: [PATCH 5/6] feat: add js timer for test_passages --- app/controllers/test_passages_controller.rb | 2 +- app/javascript/packs/application.js | 1 + app/javascript/utilities/timer.js | 20 ++++++++++++++++++++ app/views/test_passages/show.html.erb | 3 +++ config/locales/en.yml | 1 + config/locales/ru.yml | 1 + package.json | 2 +- yarn.lock | 6 +++--- 8 files changed, 31 insertions(+), 5 deletions(-) create mode 100644 app/javascript/utilities/timer.js diff --git a/app/controllers/test_passages_controller.rb b/app/controllers/test_passages_controller.rb index 64b8418..e8939b0 100644 --- a/app/controllers/test_passages_controller.rb +++ b/app/controllers/test_passages_controller.rb @@ -1,6 +1,6 @@ class TestPassagesController < ApplicationController before_action :find_test_passage, only: %i[show result update gist] - before_action :get_end_time, only: %i[update result] + before_action :get_end_time, only: %i[update result show] before_action :check_end_time, only: %i[update] def show; end diff --git a/app/javascript/packs/application.js b/app/javascript/packs/application.js index 61d5c72..fa67b59 100644 --- a/app/javascript/packs/application.js +++ b/app/javascript/packs/application.js @@ -11,6 +11,7 @@ import "utilities/sorting" import "utilities/password_check" import "utilities/form_inline" import "utilities/progress_bar" +import "utilities/timer" Rails.start() Turbolinks.start() diff --git a/app/javascript/utilities/timer.js b/app/javascript/utilities/timer.js new file mode 100644 index 0000000..02c5bb7 --- /dev/null +++ b/app/javascript/utilities/timer.js @@ -0,0 +1,20 @@ +document.addEventListener('turbolinks:load', function() { + var timerView = document.querySelector('.timer') + + if (timerView) { + var timeLeft = timerView.dataset.timer * 60 + var id = timerView.dataset.id + var countdownInterval = setInterval(function() { + if (timeLeft > 0) { + timeLeft -= 1 + } else { + clearInterval(countdownInterval) + var domain = window.location.protocol + '//' + window.location.host + window.location.href = `${domain}/test_passages/${id}/result` + } + + var resultTime = parseInt(timeLeft / 60) + ':' + timeLeft % 60 + timerView.textContent = resultTime + }, 1000) + } +}) diff --git a/app/views/test_passages/show.html.erb b/app/views/test_passages/show.html.erb index 95c0207..4058049 100644 --- a/app/views/test_passages/show.html.erb +++ b/app/views/test_passages/show.html.erb @@ -3,6 +3,9 @@ <%= content_tag :div, class: "progress-bar", data: { questions_count: @test_passage.test.questions.count, current_question_number: @test_passage.current_question.number } do %>

<% end %> +

+ <%= t('.time_left') %> <%= content_tag :span, '', class: 'timer', data: { timer: @test_passage.test.duration_time, id: @test_passage.id } %> +

<%= t('.total', count: @test_passage.test.questions.count) %>

<% unless @test_passage.completed? %>

<%= t('.currently', number: @test_passage.current_question.number) %>

diff --git a/config/locales/en.yml b/config/locales/en.yml index 88bf066..e66d9ac 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -155,6 +155,7 @@ en: currently: "You are currently taking a test: %{number}" next: 'Next' create_gist: 'Create gist' + time_left: 'Time left:' gist: success: 'Gist was successfully created, link: %{url}' failure: 'An error occurred while saving gist' diff --git a/config/locales/ru.yml b/config/locales/ru.yml index 545f781..755eba2 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -123,6 +123,7 @@ ru: currently: "Вы сейчас проходите тест: %{number}" next: 'Далее' create_gist: 'Создать gist' + time_left: 'Оставшееся время:' gist: success: 'Gist успешно сохранен, ссылка: %{url}' failure: 'Во время сохранения gist произошла ошибка' diff --git a/package.json b/package.json index 2d614c3..1991701 100644 --- a/package.json +++ b/package.json @@ -17,4 +17,4 @@ "webpack-dev-server": "^3" }, "packageManager": "yarn@1.22.19" -} \ No newline at end of file +} diff --git a/yarn.lock b/yarn.lock index 33e2e58..d51178f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1861,9 +1861,9 @@ caniuse-api@^3.0.0: lodash.uniq "^4.5.0" caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001400: - version "1.0.30001429" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001429.tgz#70cdae959096756a85713b36dd9cb82e62325639" - integrity sha512-511ThLu1hF+5RRRt0zYCf2U2yRr9GPF6m5y90SBCWsvSoYoW7yAGlv/elyPaNfvGCkp6kj/KFZWU0BMA69Prsg== + version "1.0.30001512" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001512.tgz" + integrity sha512-2S9nK0G/mE+jasCUsMPlARhRCts1ebcp2Ji8Y8PWi4NDE1iRdLCnEPHkEfeBrGC45L4isBx5ur3IQ6yTE2mRZw== case-sensitive-paths-webpack-plugin@^2.4.0: version "2.4.0" From 41012e87525a84fa288cf4798cbab2b3edbb1c2c Mon Sep 17 00:00:00 2001 From: Evil Potato Date: Fri, 7 Jul 2023 05:42:24 +0300 Subject: [PATCH 6/6] feat: change logic timer --- app/controllers/test_passages_controller.rb | 17 ++++------------- app/controllers/tests_controller.rb | 1 - app/javascript/utilities/timer.js | 7 +++---- app/models/test_passage.rb | 16 ++++++++++++++++ app/views/test_passages/show.html.erb | 2 +- ...706230219_add_timestamps_to_test_passages.rb | 6 ++++++ ...003843_add_default_duration_time_to_tests.rb | 9 +++++++++ db/schema.rb | 6 ++++-- 8 files changed, 43 insertions(+), 21 deletions(-) create mode 100644 db/migrate/20230706230219_add_timestamps_to_test_passages.rb create mode 100644 db/migrate/20230707003843_add_default_duration_time_to_tests.rb diff --git a/app/controllers/test_passages_controller.rb b/app/controllers/test_passages_controller.rb index e8939b0..4333fd9 100644 --- a/app/controllers/test_passages_controller.rb +++ b/app/controllers/test_passages_controller.rb @@ -1,13 +1,10 @@ class TestPassagesController < ApplicationController before_action :find_test_passage, only: %i[show result update gist] - before_action :get_end_time, only: %i[update result show] - before_action :check_end_time, only: %i[update] + before_action :check_timer, only: :update def show; end - def result - session[:"end_time_#{@test_passage.id}"] = nil - end + def result; end def update @test_passage.accept!(params[:answer_ids]) @@ -45,14 +42,8 @@ def gist private - def get_end_time - @end_time = session[:"end_time_#{@test_passage.id}"] - end - - def check_end_time - if @end_time.present? && @end_time <= Time.now - redirect_to result_test_passage_path(@test_passage) - end + def check_timer + redirect_to result_test_passage_path(@test_passage) if @test_passage.time_over? end def find_test_passage diff --git a/app/controllers/tests_controller.rb b/app/controllers/tests_controller.rb index 5de8715..2e2b2a5 100644 --- a/app/controllers/tests_controller.rb +++ b/app/controllers/tests_controller.rb @@ -7,6 +7,5 @@ def start @test = Test.find(params[:id]) current_user.tests.push(@test) redirect_to current_user.test_passage(@test) - session[:"end_time_#{TestPassage.last.id}"] = Time.now + @test.duration_time.minute if @test.duration_time.present? end end diff --git a/app/javascript/utilities/timer.js b/app/javascript/utilities/timer.js index 02c5bb7..ec055d5 100644 --- a/app/javascript/utilities/timer.js +++ b/app/javascript/utilities/timer.js @@ -1,16 +1,15 @@ document.addEventListener('turbolinks:load', function() { var timerView = document.querySelector('.timer') - if (timerView) { + if (timerView && timerView.dataset.timer !== "0") { var timeLeft = timerView.dataset.timer * 60 - var id = timerView.dataset.id var countdownInterval = setInterval(function() { if (timeLeft > 0) { timeLeft -= 1 } else { clearInterval(countdownInterval) - var domain = window.location.protocol + '//' + window.location.host - window.location.href = `${domain}/test_passages/${id}/result` + alert('Time is over!') + document.querySelector('form').submit() } var resultTime = parseInt(timeLeft / 60) + ':' + timeLeft % 60 diff --git a/app/models/test_passage.rb b/app/models/test_passage.rb index 3292043..5932ba8 100644 --- a/app/models/test_passage.rb +++ b/app/models/test_passage.rb @@ -29,6 +29,22 @@ def finished? def success? percent_correct_answers >= SUCCESS_RATE end + + def timer_on? + !test.duration_time.zero? + end + + def time_over? + if timer_on? + (Time.current - created_at) / 60 >= test.duration_time + else + false + end + end + + def remaining_time + remaining_minutes = (test.duration_time - (Time.current - created_at) / 60).to_i if !time_over? + end private diff --git a/app/views/test_passages/show.html.erb b/app/views/test_passages/show.html.erb index 4058049..e382a72 100644 --- a/app/views/test_passages/show.html.erb +++ b/app/views/test_passages/show.html.erb @@ -4,7 +4,7 @@
<% end %>

- <%= t('.time_left') %> <%= content_tag :span, '', class: 'timer', data: { timer: @test_passage.test.duration_time, id: @test_passage.id } %> + <%= t('.time_left') %> <%= content_tag :span, '', class: 'timer', data: { time_left: @test_passage.remaining_time } %>

<%= t('.total', count: @test_passage.test.questions.count) %>

<% unless @test_passage.completed? %> diff --git a/db/migrate/20230706230219_add_timestamps_to_test_passages.rb b/db/migrate/20230706230219_add_timestamps_to_test_passages.rb new file mode 100644 index 0000000..a4ac884 --- /dev/null +++ b/db/migrate/20230706230219_add_timestamps_to_test_passages.rb @@ -0,0 +1,6 @@ +class AddTimestampsToTestPassages < ActiveRecord::Migration[6.1] + def change + add_column :test_passages, :created_at, :datetime, default: -> { 'CURRENT_TIMESTAMP' }, null: false, precision: 6 + add_column :test_passages, :updated_at, :datetime, default: -> { 'CURRENT_TIMESTAMP' }, null: false, precision: 6 + end +end diff --git a/db/migrate/20230707003843_add_default_duration_time_to_tests.rb b/db/migrate/20230707003843_add_default_duration_time_to_tests.rb new file mode 100644 index 0000000..4cb6636 --- /dev/null +++ b/db/migrate/20230707003843_add_default_duration_time_to_tests.rb @@ -0,0 +1,9 @@ +class AddDefaultDurationTimeToTests < ActiveRecord::Migration[6.1] + def up + change_column :tests, :duration_time, :integer, default: 0, null: false + end + + def down + change_column :tests, :duration_time, :integer, default: nil, null: true + end +end diff --git a/db/schema.rb b/db/schema.rb index 8e0d797..afc8e21 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2023_07_04_222539) do +ActiveRecord::Schema.define(version: 2023_07_07_003843) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -80,6 +80,8 @@ t.integer "correct_questions", default: 0 t.bigint "current_question_id" t.boolean "finished", default: false + t.datetime "created_at", precision: 6, default: -> { "CURRENT_TIMESTAMP" }, null: false + t.datetime "updated_at", precision: 6, default: -> { "CURRENT_TIMESTAMP" }, null: false t.index ["current_question_id"], name: "index_test_passages_on_current_question_id" t.index ["test_id"], name: "index_test_passages_on_test_id" t.index ["user_id"], name: "index_test_passages_on_user_id" @@ -93,7 +95,7 @@ t.datetime "updated_at", precision: 6, null: false t.bigint "author_id" t.boolean "active", default: false - t.integer "duration_time" + t.integer "duration_time", default: 0, null: false t.index ["author_id"], name: "index_tests_on_author_id" t.index ["category_id"], name: "index_tests_on_category_id" t.index ["title", "level"], name: "index_tests_on_title_and_level", unique: true