Merge pull request #2470 from okriuchykhin/ok_SCI_4408

Enable time fraction for task due date [SCI-4408]
This commit is contained in:
Alex Kriuchykhin 2020-03-16 18:55:10 +01:00 committed by GitHub
commit 2a2e479cd3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 181 additions and 140 deletions

View file

@ -41,7 +41,7 @@ gem 'rack-attack'
# JS datetime library, requirement of datetime picker
gem 'momentjs-rails', '~> 2.17.1'
# JS datetime picker
gem 'bootstrap3-datetimepicker-rails', '~> 4.15.35'
gem 'bootstrap3-datetimepicker-rails', '~> 4.17'
# Select elements for Bootstrap
gem 'bootstrap-select-rails', '~> 1.12.4'
gem 'uglifier', '>= 1.3.0'

View file

@ -156,7 +156,7 @@ GEM
autoprefixer-rails (>= 5.2.1)
sassc (>= 2.0.0)
bootstrap-select-rails (1.12.4)
bootstrap3-datetimepicker-rails (4.15.35)
bootstrap3-datetimepicker-rails (4.17.47)
momentjs-rails (>= 2.8.1)
bootstrap_form (2.7.0)
builder (3.2.3)
@ -606,7 +606,7 @@ DEPENDENCIES
bootsnap
bootstrap-sass (~> 3.4.1)
bootstrap-select-rails (~> 1.12.4)
bootstrap3-datetimepicker-rails (~> 4.15.35)
bootstrap3-datetimepicker-rails (~> 4.17)
bootstrap_form (~> 2.7.0)
bullet
byebug

View file

@ -1,28 +1,24 @@
/* global I18n dropdownSelector */
/* eslint-disable no-use-before-define */
function updateDueDate() {
let updateUrl = $('.due-date-container').data('update-url');
let val = $('#calendar-due-date').val();
$.ajax({
url: updateUrl,
type: 'PATCH',
dataType: 'json',
data: { my_module: { due_date: val } },
success: function(result) {
$('#due-date-label-container').html(result.due_date_label);
}
});
}
// Bind ajax for editing due dates
function initDueDatePicker() {
function updateDueDate(val) {
var updateUrl = $('.due-date-container').data('update-url');
$.ajax({
url: updateUrl,
type: 'PATCH',
dataType: 'json',
data: { my_module: { due_date: val } },
success: function(result) {
$('.due-date-container').html($(result.module_header_due_date_label));
initDueDatePicker();
}
});
}
$('#calendar-due-date').on('dp.change', function() {
updateDueDate($('#calendar-due-date').val());
});
$('.flex-block.date-block .clear-date').off('click').on('click', function() {
updateDueDate(null);
updateDueDate();
});
}
@ -194,7 +190,7 @@ function applyTaskCompletedCallBack() {
button.find('.btn')
.removeClass('btn-default').addClass('btn-primary');
}
$('.due-date-container').html(data.module_header_due_date_label);
$('.due-date-container').html(data.module_header_due_date);
initDueDatePicker();
$('.task-state-label').html(data.module_state_label);
button.find('button').replaceWith(data.new_btn);

View file

@ -750,7 +750,7 @@ function bindEditDueDateAjax() {
editDueDateModalBody.find("form")
.on("ajax:success", function(ev2, data2, status2) {
// Update module's due date
dueDateLink.html(data2.due_date_label);
dueDateLink.html(data2.card_due_date_label);
// Update module's classes if needed
moduleEl

View file

@ -41,7 +41,7 @@
task_button.attr('data-action', 'uncomplete-task');
task_button.find('.btn')
.removeClass('btn-toggle').addClass('btn-default');
$('.task-due-date').html(data.module_header_due_date_label);
$('.task-due-date').html(data.module_header_due_date);
$('.task-state-label').html(data.module_state_label);
task_button
.find('button')

View file

@ -0,0 +1,24 @@
(function() {
'use strict';
$(document).on('click', '[data-toggle="date-time-picker"]', function(ev) {
ev.preventDefault();
ev.stopPropagation();
let dt = $(this);
if (dt.data('DateTimePicker')) {
dt.data('DateTimePicker').destroy();
}
dt.datetimepicker({ ignoreReadonly: true });
dt.data('DateTimePicker').show();
});
$(document).on('click', '[data-toggle="clear-date-time-picker"]', function() {
let dt = $(`#${$(this).data('target')}`);
if (!dt.data('DateTimePicker')) dt.datetimepicker({ useCurrent: false });
dt.data('DateTimePicker').clear();
dt.val('');
});
}());

View file

@ -63,15 +63,15 @@
padding: 0 3px;
position: relative;
&.alert-green {
.alert-green {
color: $brand-success;
}
&.alert-yellow {
.alert-yellow {
color: $brand-warning;
}
&.alert-red {
.alert-red {
color: $brand-danger;
}

View file

@ -145,10 +145,8 @@ class MyModulesController < ApplicationController
def update
update_params = my_module_params
if update_params[:due_date].present?
update_params[:due_date] = Time.strptime(
update_params[:due_date].delete('-'),
I18n.backend.date_format.dup.delete('-')
)
update_params[:due_date] =
Time.zone.strptime(update_params[:due_date], I18n.backend.date_format.dup.gsub(/%-/, '%') + ' %H:%M')
end
@my_module.assign_attributes(update_params)
@my_module.last_modified_by = current_user
@ -211,15 +209,19 @@ class MyModulesController < ApplicationController
render json: {
status: :ok,
due_date_label: render_to_string(
partial: "my_modules/due_date_label.html.erb",
partial: 'my_modules/due_date_label.html.erb',
locals: { my_module: @my_module }
),
module_header_due_date_label: render_to_string(
partial: "my_modules/module_header_due_date_label.html.erb",
card_due_date_label: render_to_string(
partial: 'my_modules/card_due_date_label.html.erb',
locals: { my_module: @my_module }
),
module_header_due_date: render_to_string(
partial: 'my_modules/module_header_due_date.html.erb',
locals: { my_module: @my_module }
),
description_label: render_to_string(
partial: "my_modules/description_label.html.erb",
partial: 'my_modules/description_label.html.erb',
locals: { my_module: @my_module }
),
alerts: alerts
@ -577,8 +579,8 @@ class MyModulesController < ApplicationController
render json: {
new_btn: render_to_string(partial: new_btn_partial),
completed: completed,
module_header_due_date_label: render_to_string(
partial: 'my_modules/module_header_due_date_label.html.erb',
module_header_due_date: render_to_string(
partial: 'my_modules/module_header_due_date.html.erb',
locals: { my_module: @my_module }
),
module_state_label: render_to_string(
@ -605,8 +607,8 @@ class MyModulesController < ApplicationController
format.json do
render json: {
task_button_title: t('my_modules.buttons.uncomplete'),
module_header_due_date_label: render_to_string(
partial: 'my_modules/module_header_due_date_label.html.erb',
module_header_due_date: render_to_string(
partial: 'my_modules/module_header_due_date.html.erb',
locals: { my_module: @my_module }
),
module_state_label: render_to_string(

View file

@ -22,9 +22,10 @@ module BootstrapFormHelper
def datetime_picker(name, options = {})
id = "#{@object_name}_#{name.to_s}"
input_name = "#{@object_name}[#{name.to_s}]"
timestamp = @object[name] ? "#{@object[name].to_i}000" : ""
date_format = I18n.backend.date_format.dup
value = options[:value] ? options[:value].strftime(date_format + ' %H:%M') : ''
js_locale = I18n.locale.to_s
js_format = I18n.backend.date_format.dup
js_format = date_format
js_format.gsub!(/%-d/, 'D')
js_format.gsub!(/%d/, 'DD')
js_format.gsub!(/%-m/, 'M')
@ -32,36 +33,27 @@ module BootstrapFormHelper
js_format.gsub!(/%b/, 'MMM')
js_format.gsub!(/%B/, 'MMMM')
js_format.gsub!('%Y', 'YYYY')
js_format << ' HH:mm' if options[:time] == true
label = name.to_s.humanize
if options[:label] then
label = options[:label]
label = options[:label] || name.to_s.humanize
style = options[:style] ? "style='#{options[:style]}'" : ''
res = "<div class='form-group' #{style}><label class='control-label required' for='#{id}'>#{label}</label>" \
"<div class='container' style='padding-left: 0; margin-left: 0;'><div class='row'><div class='col-sm-6'>"
res << "<div class='input-group date'>" if options[:clear]
res << "<input type='datetime' class='form-control' name='#{input_name}' id='#{id}' "\
"readonly value='#{value}' data-toggle='date-time-picker' data-date-format='#{js_format}' " \
"data-date-locale='#{js_locale}' data-date-show-today-button='#{options[:today].present?}'/>"
if options[:clear]
res << "<span class='input-group-addon' data-toggle='clear-date-time-picker' data-target='#{id}'>" \
"<i class='fas fa-times'></i></span></div>"
end
styleStr = ""
if options[:style] then
styleStr = "style='#{options[:style]}'"
end
jsOpts = ""
if options[:today] then
jsOpts << "showTodayButton: true, "
end
res = ""
res << "<div class='form-group' #{styleStr}><label class='control-label required' for='#{id}'>#{label}</label><div class='container' style='padding-left: 0; margin-left: 0;'><div class='row'><div class='col-sm-6'><div class='form-group'>"
if options[:clear] then
res << "<div class='input-group date'>"
end
res << "<input type='datetime' class='form-control' name='#{input_name}' id='#{id}' readonly data-ts='#{timestamp}' />"
if options[:clear] then
res << "<span class='input-group-addon' id='#{id}_clear'><span class='fas fa-times'></span></span></div>"
end
res << "</div></div></div></div><script type='text/javascript'>$(function () { var dt = $('##{id}'); dt.datetimepicker({ #{jsOpts}ignoreReadonly: true, locale: '#{js_locale}', format: '#{js_format}' }); if (dt.length > 0 && dt.data['ts'] != '') { $('##{id}').data('DateTimePicker').date(moment($('##{id}').data('ts'))); }"
if options[:clear] then
res << "$('##{id}_clear').click(function() { $('##{id}').data('DateTimePicker').clear(); });"
end
res << "});</script></div>"
res << '</div></div></div></div>'
res.html_safe
end

View file

@ -271,14 +271,14 @@ class MyModule < ApplicationRecord
end
def is_overdue?(datetime = DateTime.current)
due_date.present? && datetime.utc > due_date.end_of_day.utc
due_date.present? && datetime.utc > due_date.utc
end
def overdue_for_days(datetime = DateTime.current)
if due_date.blank? || due_date.end_of_day.utc > datetime.utc
if due_date.blank? || due_date.utc > datetime.utc
0
else
((datetime.utc.to_i - due_date.end_of_day.utc.to_i) / 1.day.to_f).ceil
((datetime.utc.to_i - due_date.utc.to_i) / 1.day.to_f).ceil
end
end
@ -288,8 +288,8 @@ class MyModule < ApplicationRecord
def is_due_in?(datetime, diff)
due_date.present? &&
datetime.utc < due_date.end_of_day.utc &&
datetime.utc > (due_date.end_of_day.utc - diff)
datetime.utc < due_date.utc &&
datetime.utc > (due_date.utc - diff)
end
def space_taken

View file

@ -29,17 +29,17 @@
</div>
<div class="panel-body">
<% if can_manage_module?(my_module) %>
<% if !my_module.completed? && can_manage_module?(my_module) %>
<%= link_to due_date_my_module_path(my_module, format: :json), remote: true,
class: "due-date-link due-date-refresh help_tooltips",
data: {
tooltiplink: I18n.t('tooltips.link.task.due_date'),
tooltipcontent: I18n.t('tooltips.text.task.due_date')
} do %>
<%= render partial: "my_modules/due_date_label.html.erb", locals: { my_module: my_module } %>
<%= render partial: "my_modules/card_due_date_label.html.erb", locals: { my_module: my_module, format: :full_date } %>
<% end %>
<% else %>
<%= render partial: "my_modules/due_date_label.html.erb", locals: { my_module: my_module } %>
<%= render partial: "my_modules/card_due_date_label.html.erb", locals: { my_module: my_module, format: :full_date } %>
<% end %>
</div>

View file

@ -0,0 +1,29 @@
<% if my_module.completed? %>
<%= t("my_modules.states.completed") %>
<span class="due-date-label">
<%= l(my_module.completed_on.utc, format: :full_date) %>
<span class="fas fa-check"></span>
</span>
<% elsif my_module.is_one_day_prior? %>
<%= t("my_modules.states.due_soon") %>
<span class="due-date-label">
<%=l my_module.due_date.utc, format: :full_date %>
<span class="fas fa-exclamation-triangle"></span>
</span>
<% elsif my_module.is_overdue? %>
<%= t("my_modules.states.overdue") %>
<span class="due-date-label">
<%=l my_module.due_date.utc, format: :full_date %>
<span class="fas fa-exclamation-triangle"></span>
</span>
<% elsif my_module.due_date %>
<%= t("experiments.canvas.full_zoom.due_date") %>
<span class="due-date-label">
<%=l my_module.due_date.utc, format: :full_date %>
</span>
<% else %>
<%= t("experiments.canvas.full_zoom.due_date") %>
<span class="due-date-label">
<em><%=t "experiments.canvas.full_zoom.no_due_date" %></em>
</span>
<% end %>

View file

@ -1,3 +1,7 @@
<%= bootstrap_form_for @my_module, url: my_module_path(@my_module, format: :json), remote: :true do |f| %>
<%= f.datetime_picker :due_date, label: t("my_modules.due_date.label"), clear: true %>
<%= f.datetime_picker :due_date,
value: @my_module.due_date,
label: t('my_modules.due_date.label'),
clear: true,
time: true %>
<% end %>

View file

@ -1,29 +1,19 @@
<% if my_module.completed? %>
<%= t("my_modules.states.completed") %>
<span class="due-date-label">
<%= l(my_module.completed_on.utc, format: :full_date) %>
<span class="fas fa-check"></span>
</span>
<% elsif my_module.is_one_day_prior? %>
<%= t("my_modules.states.due_soon") %>
<span class="due-date-label">
<%=l my_module.due_date.utc, format: :full_date %>
<span class="fas fa-exclamation-triangle"></span>
</span>
<% elsif my_module.is_overdue? %>
<%= t("my_modules.states.overdue") %>
<span class="due-date-label">
<%=l my_module.due_date.utc, format: :full_date %>
<span class="fas fa-exclamation-triangle"></span>
</span>
<% elsif my_module.due_date %>
<%= t("experiments.canvas.full_zoom.due_date") %>
<span class="due-date-label">
<%=l my_module.due_date.utc, format: :full_date %>
</span>
<% else %>
<%= t("experiments.canvas.full_zoom.due_date") %>
<span class="due-date-label">
<em><%=t "experiments.canvas.full_zoom.no_due_date" %></em>
</span>
<% end %>
<span class="due-date-label <%= get_task_alert_color(my_module) %>">
<% if my_module.completed? %>
<%= t('my_modules.states.completed') %>
<%= l(my_module.completed_on, format: :full) %>
<span class="fas fa-check"></span>
<% elsif my_module.is_one_day_prior? %>
<%= t('my_modules.states.due_soon') %>
<%= l(my_module.due_date, format: :full) %>
<span class="fas fa-exclamation-triangle"></span>
<% elsif my_module.is_overdue? %>
<%= t('my_modules.states.overdue') %>
<%= l(my_module.due_date, format: :full) %>
<span class="fas fa-exclamation-triangle"></span>
<% elsif my_module.due_date %>
<%= l(my_module.due_date, format: :full) %>
<% else %>
<em><%= t('experiments.canvas.full_zoom.no_due_date') %></em>
<% end %>
</span>

View file

@ -6,12 +6,12 @@
</div>
<strong><%= l(@my_module.created_at, format: :full) %></strong>
</div>
<div class="flex-block date-block" >
<span class="fas block-icon fa-calendar-alt"></span>
<span class="hidden-xs hidden-sm hidden-md"><%=t "my_modules.module_header.due_date" %></span>
<div class="due-date-container" data-update-url="<%= my_module_path(@my_module, format: :json) %>">
<%= render partial: "module_header_due_date_label.html.erb",
<%= render partial: "module_header_due_date.html.erb",
locals: { my_module: @my_module } %>
</div>
</div>

View file

@ -0,0 +1,33 @@
<span class="date-text" data-editable="<%= can_manage_module?(my_module) %>">
<span id="due-date-label-container" class="view-block">
<%= render partial: "due_date_label.html.erb" , locals: { my_module: my_module } %>
</span>
<% if can_manage_module?(my_module) %>
<div class="datetime-picker-container" id="due-date">
<%
js_format = I18n.backend.date_format.dup
js_format.gsub!(/%-d/, 'D')
js_format.gsub!(/%d/, 'DD')
js_format.gsub!(/%-m/, 'M')
js_format.gsub!(/%m/, 'MM')
js_format.gsub!(/%b/, 'MMM')
js_format.gsub!(/%B/, 'MMMM')
js_format.gsub!('%Y', 'YYYY')
js_format << ' HH:mm'
%>
<span class="fas fa-calendar-alt"></span>
<input id="calendar-due-date"
type="datetime"
data-toggle='date-time-picker'
class="form-control calendar-input"
readonly
placeholder="<%= t('experiments.canvas.full_zoom.no_due_date') %>"
data-date-format="<%= js_format %>"
data-date-locale="<%= I18n.locale %>"
data-date-use-current="false"
value="<%= my_module.due_date ? l(my_module.due_date, format: :full) : '' %>"/>
</div>
<span class="fas fa-times clear-date" data-toggle='clear-date-time-picker' data-target='calendar-due-date'></span>
<% end %>
</span>

View file

@ -1,29 +0,0 @@
<%
if my_module.completed?
date_class = "alert-green"
elsif my_module.is_one_day_prior?
date_class = "alert-yellow"
label = "(#{t('my_modules.states.due_soon')})"
elsif my_module.is_overdue?
date_class = "alert-red"
label = "(#{t('my_modules.states.overdue')})"
end
%>
<span class="date-text <%= date_class if my_module.due_date %>" data-editable="<%= can_manage_module?(my_module) %>">
<span class="view-block">
<%= my_module.due_date ? l(my_module.due_date.utc, format: :full) : t("experiments.canvas.full_zoom.no_due_date") %>
</span>
<% if can_manage_module?(my_module) %>
<%= render partial: "global_activities/date_picker.html.erb", locals: {
id: 'due-date',
use_current: 'false',
setDate: my_module.due_date ? l(my_module.due_date.utc, format: :full) : '',
placeholder: t("experiments.canvas.full_zoom.no_due_date"),
label: nil
} %>
<% end %>
<%= label %>
<% if my_module.due_date && can_manage_module?(my_module) %>
<span class="fas fa-times clear-date"></span>
<% end %>
</span>

View file

@ -63,7 +63,7 @@ describe MyModulesController, type: :controller do
context 'when setting due_date' do
let(:params) do
{ id: my_module.id, my_module: { due_date: '03/21/2019' } }
{ id: my_module.id, my_module: { due_date: '03/21/2019 23:59' } }
end
it 'calls create activity for setting due date' do
@ -102,7 +102,7 @@ describe MyModulesController, type: :controller do
context 'when updating due_date' do
let(:params) do
{ id: my_module.id, my_module: { due_date: '02/21/2019' } }
{ id: my_module.id, my_module: { due_date: '02/21/2019 23:59' } }
end
let(:my_module) do
create :my_module, :with_due_date, experiment: experiment