Add start date to the task [SCI-4504]

This commit is contained in:
Oleksii Kriuchykhin 2020-03-27 17:35:24 +01:00
parent 404be29fc0
commit 3c53be1f9a
19 changed files with 191 additions and 115 deletions

View file

@ -1,23 +1,44 @@
/* global I18n dropdownSelector */
/* eslint-disable no-use-before-define */
function updateStartDate() {
let updateUrl = $('#startDateContainer').data('update-url');
let val = $('#calendarStartDate').val();
$.ajax({
url: updateUrl,
type: 'PATCH',
dataType: 'json',
data: { my_module: { started_on: val } },
success: function(result) {
$('#startDateLabelContainer').html(result.start_date_label);
}
});
}
// Bind ajax for editing due dates
function initStartDatePicker() {
$('#calendarStartDate').on('dp.change', function() {
updateStartDate();
});
}
function updateDueDate() {
let updateUrl = $('.due-date-container').data('update-url');
let val = $('#calendar-due-date').val();
let updateUrl = $('#dueDateContainer').data('update-url');
let val = $('#calendarDueDate').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);
$('#dueDateLabelContainer').html(result.due_date_label);
}
});
}
// Bind ajax for editing due dates
function initDueDatePicker() {
$('#calendar-due-date').on('dp.change', function() {
$('#calendarDueDate').on('dp.change', function() {
updateDueDate();
});
}
@ -190,7 +211,7 @@ function applyTaskCompletedCallBack() {
button.find('.btn')
.removeClass('btn-default').addClass('btn-primary');
}
$('.due-date-container').html(data.module_header_due_date);
$('#dueDateContainer').html(data.module_header_due_date);
initDueDatePicker();
$('.task-state-label').html(data.module_state_label);
button.find('button').replaceWith(data.new_btn);
@ -272,4 +293,5 @@ function initTagsSelector() {
applyTaskCompletedCallBack();
initTagsSelector();
bindEditTagsAjax();
initStartDatePicker();
initDueDatePicker();

View file

@ -77,7 +77,7 @@
}
}
.due-date-container {
.datetime-container {
align-items: center;
display: inline-flex;
@ -121,7 +121,11 @@
top: 0;
width: 100%;
#calendar-due-date {
#calendarDueDate {
opacity: 0;
}
#calendarStartDate {
opacity: 0;
}

View file

@ -143,21 +143,15 @@ class MyModulesController < ApplicationController
end
def update
update_params = my_module_params
if update_params[:due_date].present?
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.assign_attributes(my_module_params)
@my_module.last_modified_by = current_user
description_changed = @my_module.description_changed?
start_date_changes = @my_module.changes[:started_on]
due_date_changes = @my_module.changes[:due_date]
if @my_module.archived_changed?(from: false, to: true)
saved = @my_module.archive(current_user)
elsif @my_module.archived_changed?(from: true, to: false)
saved = @my_module.restore(current_user)
if saved
restored = true
@ -165,28 +159,14 @@ class MyModulesController < ApplicationController
end
else
saved = @my_module.save
if saved
if description_changed
log_activity(:change_module_description)
TinyMceAsset.update_images(@my_module, params[:tiny_mce_images], current_user)
end
if due_date_changes
# rubocop:disable Metrics/BlockNesting # temporary solution
type_of = if due_date_changes[0].nil? # set due_date
message_items = { my_module_duedate: @my_module.due_date }
:set_task_due_date
elsif due_date_changes[1].nil? # remove due_date
message_items = { my_module_duedate: due_date_changes[0] }
:remove_task_due_date
else # change due_date
message_items = { my_module_duedate: @my_module.due_date }
:change_task_due_date
end
# rubocop:enable Metrics/BlockNesting
log_activity(type_of, @my_module, message_items)
end
log_start_date_change_activity(start_date_changes) if start_date_changes.present?
log_due_date_change_activity(due_date_changes) if due_date_changes.present?
end
end
respond_to do |format|
@ -199,7 +179,7 @@ class MyModulesController < ApplicationController
redirect_to module_archive_experiment_path(@my_module.experiment)
end
elsif saved
format.json {
format.json do
alerts = []
alerts << 'alert-green' if @my_module.completed?
unless @my_module.completed?
@ -208,6 +188,10 @@ class MyModulesController < ApplicationController
end
render json: {
status: :ok,
start_date_label: render_to_string(
partial: 'my_modules/start_date_label.html.erb',
locals: { my_module: @my_module }
),
due_date_label: render_to_string(
partial: 'my_modules/due_date_label.html.erb',
locals: { my_module: @my_module }
@ -226,12 +210,12 @@ class MyModulesController < ApplicationController
),
alerts: alerts
}
}
end
else
format.json {
format.json do
render json: @my_module.errors,
status: :unprocessable_entity
}
end
end
end
end
@ -725,8 +709,46 @@ class MyModulesController < ApplicationController
end
def my_module_params
params.require(:my_module).permit(:name, :description, :due_date,
:archived)
update_params = params.require(:my_module).permit(:name, :description, :started_on, :due_date, :archived)
if update_params[:started_on].present?
update_params[:started_on] =
Time.zone.strptime(update_params[:started_on], I18n.backend.date_format.dup.gsub(/%-/, '%') + ' %H:%M')
end
if update_params[:due_date].present?
update_params[:due_date] =
Time.zone.strptime(update_params[:due_date], I18n.backend.date_format.dup.gsub(/%-/, '%') + ' %H:%M')
end
update_params
end
def log_start_date_change_activity(start_date_changes)
type_of = if start_date_changes[0].nil? # set started_on
message_items = { my_module_started_on: @my_module.due_date }
:set_task_start_date
elsif start_date_changes[1].nil? # remove started_on
message_items = { my_module_started_on: start_date_changes[0] }
:remove_task_start_date
else # change started_on
message_items = { my_module_started_on: @my_module.due_date }
:change_task_start_date
end
log_activity(type_of, @my_module, message_items)
end
def log_due_date_change_activity(due_date_changes)
type_of = if due_date_changes[0].nil? # set due_date
message_items = { my_module_duedate: @my_module.due_date }
:set_task_due_date
elsif due_date_changes[1].nil? # remove due_date
message_items = { my_module_duedate: due_date_changes[0] }
:remove_task_due_date
else # change due_date
message_items = { my_module_duedate: @my_module.due_date }
:change_task_due_date
end
log_activity(type_of, @my_module, message_items)
end
def log_activity(type_of, my_module = nil, message_items = {})
@ -747,5 +769,4 @@ class MyModulesController < ApplicationController
:page, :starting_timestamp, :from_date, :to_date, types: [], users: [], subjects: {}
)
end
end

View file

@ -22,18 +22,9 @@ module BootstrapFormHelper
def datetime_picker(name, options = {})
id = "#{@object_name}_#{name.to_s}"
input_name = "#{@object_name}[#{name.to_s}]"
date_format = I18n.backend.date_format.dup
value = options[:value] ? options[:value].strftime(date_format + ' %H:%M') : ''
value = options[:value] ? options[:value].strftime("#{I18n.backend.date_format} %H:%M") : ''
js_locale = I18n.locale.to_s
js_format = date_format
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' if options[:time] == true
js_format = options[:time] ? datetime_picker_format_full : datetime_picker_format_date_only
label = options[:label] || name.to_s.humanize
@ -266,4 +257,24 @@ module BootstrapFormHelper
text_area(name, options)
end
end
# Returns date only format string for Bootstrap DateTimePicker
def datetime_picker_format_date_only
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
end
# Returns date and time format string for Bootstrap DateTimePicker
def datetime_picker_format_full
js_format = datetime_picker_format_date_only
js_format << ' HH:mm'
js_format
end
end

View file

@ -4,7 +4,7 @@ module Api
module V1
class TaskSerializer < ActiveModel::Serializer
type :tasks
attributes :id, :name, :due_date, :description, :state, :archived
attributes :id, :name, :started_on, :due_date, :description, :state, :archived
has_many :output_tasks, key: :outputs,
serializer: TaskSerializer,
class_name: 'MyModule'

View file

@ -6,16 +6,5 @@
</div>
<script type="text/javascript" charset="utf-8">
<%
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')
%>
var formatJS = "<%= js_format %>"
var formatJS = "<%= datetime_picker_format_date_only %>"
</script>

View file

@ -4,20 +4,11 @@
<% end %>
<span class="fas fa-calendar-alt"></span>
<input type="datetime" class="form-control calendar-input" name="calendar[<%= id %>]" id="calendar-<%= id %>" readonly="" data-ts="" placeholder="<%= placeholder %>" value="<%= defined?(setDate) ? setDate : '' %>"/>
<%
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')
%>
<script type="text/javascript">
$(function () {
var dt = $('#calendar-<%= id %>');
dt.datetimepicker({ useCurrent: <%= use_current %>, ignoreReadonly: true, locale: '<%= I18n.locale %>', format: '<%= js_format %>' });
dt.datetimepicker({ useCurrent: <%= use_current %>, ignoreReadonly: true, locale: '<%= I18n.locale %>', format: '<%= datetime_picker_format_date_only %>' });
});
</script>
</div>

View file

@ -1,33 +1,23 @@
<span class="date-text" data-editable="<%= can_manage_module?(my_module) %>">
<span id="due-date-label-container" class="view-block">
<% my_module_editable = can_manage_module?(my_module) %>
<span class="date-text" data-editable="<%= my_module_editable %>">
<span id="dueDateLabelContainer" class="view-block">
<%= render partial: "due_date_label.html.erb" , locals: { my_module: my_module } %>
</span>
<% if can_manage_module?(my_module) %>
<% if my_module_editable %>
<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"
<input id="calendarDueDate"
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-format="<%= datetime_picker_format_full %>"
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>
<span class="fas fa-times clear-date" data-toggle='clear-date-time-picker' data-target='calendarDueDate'></span>
<% end %>
</span>

View file

@ -0,0 +1,23 @@
<% my_module_editable = can_manage_module?(my_module) %>
<span class="date-text" data-editable="<%= my_module_editable %>">
<span id="startDateLabelContainer" class="view-block">
<%= render partial: "start_date_label.html.erb" , locals: { my_module: my_module } %>
</span>
<% if my_module_editable %>
<div class="datetime-picker-container" id="start-date">
<span class="fas fa-calendar-alt"></span>
<input id="calendarStartDate"
type="datetime"
data-toggle='date-time-picker'
class="form-control calendar-input"
readonly
placeholder="<%= t('my_modules.module_header.no_start_date_placeholder') %>"
data-date-format="<%= datetime_picker_format_full %>"
data-date-locale="<%= I18n.locale %>"
data-date-use-current="false"
value="<%= my_module.started_on ? l(my_module.started_on, format: :full) : '' %>"/>
</div>
<span class="fas fa-times clear-date" data-toggle='clear-date-time-picker' data-target='calendarStartDate'></span>
<% end %>
</span>

View file

@ -1,18 +1,20 @@
<% my_module_editable = can_manage_module?(@my_module) %>
<div class="header-container">
<div class="flex-block date-block">
<div class="flex-block-label">
<span class="fas block-icon fa-calendar-alt"></span>
<span class="hidden-xs hidden-sm hidden-md"><%=t "my_modules.module_header.start_date" %></span>
<span class="fas block-icon fa-calendar-alt"></span>
<span class="hidden-xs hidden-sm hidden-md"><%= t('my_modules.module_header.start_date') %></span>
<div id="startDateContainer" class="datetime-container" data-update-url="<%= my_module_path(@my_module, format: :json) %>">
<%= render partial: "module_header_start_date.html.erb", locals: { my_module: @my_module } %>
</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.html.erb",
locals: { my_module: @my_module } %>
<span class="hidden-xs hidden-sm hidden-md"><%= t('my_modules.module_header.due_date') %></span>
<div id="dueDateContainer" class="datetime-container" data-update-url="<%= my_module_path(@my_module, format: :json) %>">
<%= render partial: "module_header_due_date.html.erb", locals: { my_module: @my_module } %>
</div>
</div>
@ -36,7 +38,7 @@
<div id="module-tags" data-module-tags-url="<%= my_module_my_module_tags_url(@my_module, format: :json) %>">
<span class="fas block-icon fa-tags"></span>
<span class="hidden-xs hidden-sm tags-title"><%=t "my_modules.module_header.tags" %></span>
<%= render partial: "my_modules/tags", locals: { my_module: @my_module, editable: can_manage_module?(@my_module) } %>
<%= render partial: "my_modules/tags", locals: { my_module: @my_module, editable: my_module_editable } %>
</div>
</div>

View file

@ -0,0 +1,7 @@
<span class="start-date-label">
<% if my_module.started_on.present? %>
<strong><%= l(my_module.started_on, format: :full) %></strong>
<% else %>
<%= t('my_modules.module_header.no_start_date') %>
<% end %>
</span>

View file

@ -24,6 +24,13 @@
<% end %>
</h4>
</div>
<div class="pull-right module-start-date">
<% if my_module.started_on.present? %>
<%= t('projects.reports.elements.module.started_on', start_date: l(my_module.started_on, format: :full)) %>
<% else %>
<em><%=t "projects.reports.elements.module.no_start_date" %></em>
<% end %>
</div>
<div class="pull-right module-due-date">
<% if my_module.due_date.present? %>
<%=t "projects.reports.elements.module.due_date", due_date: l(my_module.due_date, format: :full) %>

View file

@ -188,16 +188,5 @@
<%= javascript_pack_tag 'emoji_button' %>
<script type="text/javascript" charset="utf-8">
<%
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')
%>
var formatJS = "<%= js_format %>"
var formatJS = "<%= datetime_picker_format_date_only %>"
</script>

View file

@ -279,7 +279,10 @@ class Extends
share_inventory_with_all: 134,
unshare_inventory_with_all: 135,
update_share_with_all_permission_level: 136,
protocol_description_in_task_edited: 137
protocol_description_in_task_edited: 137,
set_task_start_date: 138,
change_task_start_date: 139,
remove_task_start_date: 140
}
ACTIVITY_GROUPS = {

View file

@ -500,6 +500,8 @@ en:
title: "Report for project %{project}"
module:
user_time: "Task created on %{timestamp}."
started_on: "Start date: %{started_on}"
no_start_date: "No start date"
due_date: "Due date: %{due_date}"
no_due_date: "No due date"
no_description: "No description"
@ -637,6 +639,8 @@ en:
restored_flash: "Task %{module} restored successfully!"
module_header:
start_date: "Start date:"
no_start_date: "not set"
no_start_date_placeholder: "+ Add starting date"
due_date: "Due date:"
tags: "Tags:"
no_tags: "Add new Task Tags (optional)"

View file

@ -100,6 +100,9 @@ en:
rename_task_html: "%{user} renamed task %{my_module}."
move_task_html: "%{user} moved task %{my_module} from experiment %{experiment_original} to experiment %{experiment_new}."
archive_module_html: "%{user} archived task %{my_module}."
set_task_start_date_html: "%{user} set due date %{my_module_started_on} on task %{my_module}."
change_task_start_date_html: "%{user} changed start date %{my_module_started_on} on task %{my_module}."
remove_task_start_date_html: "%{user} removed start date %{my_module_started_on} on task %{my_module}."
set_task_due_date_html: "%{user} set due date %{my_module_duedate} on task %{my_module}."
change_task_due_date_html: "%{user} changed due date %{my_module_duedate} on task %{my_module}."
remove_task_due_date_html: "%{user} removed due date %{my_module_duedate} on task %{my_module}."

View file

@ -0,0 +1,7 @@
# frozen_string_literal: true
class AddStartDateToTask < ActiveRecord::Migration[6.0]
def change
add_column :my_modules, :started_on, :datetime, null: true
end
end

View file

@ -667,7 +667,8 @@ CREATE TABLE public.my_modules (
workflow_order integer DEFAULT '-1'::integer NOT NULL,
experiment_id bigint DEFAULT 0 NOT NULL,
state smallint DEFAULT 0,
completed_on timestamp without time zone
completed_on timestamp without time zone,
started_on timestamp without time zone
);
@ -7167,6 +7168,7 @@ INSERT INTO "schema_migrations" (version) VALUES
('20191210103004'),
('20191218072619'),
('20200113143828'),
('20200204100934');
('20200204100934'),
('20200326114643');

View file

@ -35,6 +35,7 @@ describe MyModule, type: :model do
it { should have_db_column :experiment_id }
it { should have_db_column :state }
it { should have_db_column :completed_on }
it { should have_db_column :started_on }
end
describe 'Relations' do