From 2e8152abc40304e3976a8ad0598c13a500ac41cf Mon Sep 17 00:00:00 2001 From: Anton Date: Fri, 30 May 2025 13:04:45 +0200 Subject: [PATCH] Add experiments and tasks counter to project table [SCI-11944] --- .../experiments/renderers/completed_tasks.vue | 43 +++++------------ app/javascript/vue/projects/list.vue | 20 +++++++- .../renderers/completed_experiments.vue | 25 ++++++++++ .../projects/renderers/completed_tasks.vue | 25 ++++++++++ .../shared/datatable/renderers/counter.vue | 47 +++++++++++++++++++ .../lists/project_and_folder_serializer.rb | 29 +++++++++--- app/services/lists/experiments_service.rb | 22 +++++---- app/services/lists/projects_service.rb | 43 +++++++++++------ config/locales/en.yml | 2 + .../lists/experiments_service_spec.rb | 4 ++ spec/services/lists/projects_service_spec.rb | 4 ++ 11 files changed, 203 insertions(+), 61 deletions(-) create mode 100644 app/javascript/vue/projects/renderers/completed_experiments.vue create mode 100644 app/javascript/vue/projects/renderers/completed_tasks.vue create mode 100644 app/javascript/vue/shared/datatable/renderers/counter.vue diff --git a/app/javascript/vue/experiments/renderers/completed_tasks.vue b/app/javascript/vue/experiments/renderers/completed_tasks.vue index bc63741a9..4ea66ab81 100644 --- a/app/javascript/vue/experiments/renderers/completed_tasks.vue +++ b/app/javascript/vue/experiments/renderers/completed_tasks.vue @@ -1,43 +1,24 @@ diff --git a/app/javascript/vue/projects/list.vue b/app/javascript/vue/projects/list.vue index 88dbb53ce..98651b703 100644 --- a/app/javascript/vue/projects/list.vue +++ b/app/javascript/vue/projects/list.vue @@ -81,6 +81,8 @@ import DataTable from '../shared/datatable/table.vue'; import UsersRenderer from './renderers/users.vue'; import NameRenderer from './renderers/name.vue'; import StatusRenderer from './renderers/status.vue'; +import CompletedTasksRenderer from './renderers/completed_tasks.vue'; +import CompletedExperimentsRenderer from './renderers/completed_experiments.vue'; import SuperviserRenderer from './renderers/superviser.vue'; import CommentsRenderer from '../shared/datatable/renderers/comments.vue'; import DueDateRenderer from '../shared/datatable/renderers/date.vue'; @@ -115,7 +117,9 @@ export default { DescriptionModal, StatusRenderer, SuperviserRenderer, - FavoriteRenderer + FavoriteRenderer, + CompletedTasksRenderer, + CompletedExperimentsRenderer }, props: { dataSource: { type: String, required: true }, @@ -226,6 +230,20 @@ export default { cellRenderer: SuperviserRenderer, notSelectable: true }, + { + field: 'completed_experiments', + headerName: this.i18n.t('projects.index.card.completed_experiment'), + cellRenderer: CompletedExperimentsRenderer, + sortable: true, + minWidth: 110 + }, + { + field: 'completed_tasks', + headerName: this.i18n.t('experiments.table.column.completed_task'), + cellRenderer: CompletedTasksRenderer, + sortable: true, + minWidth: 110 + }, { field: 'created_at', headerName: this.i18n.t('projects.index.card.start_date'), diff --git a/app/javascript/vue/projects/renderers/completed_experiments.vue b/app/javascript/vue/projects/renderers/completed_experiments.vue new file mode 100644 index 000000000..aea45a8ed --- /dev/null +++ b/app/javascript/vue/projects/renderers/completed_experiments.vue @@ -0,0 +1,25 @@ + + + diff --git a/app/javascript/vue/projects/renderers/completed_tasks.vue b/app/javascript/vue/projects/renderers/completed_tasks.vue new file mode 100644 index 000000000..e9a70abca --- /dev/null +++ b/app/javascript/vue/projects/renderers/completed_tasks.vue @@ -0,0 +1,25 @@ + + + diff --git a/app/javascript/vue/shared/datatable/renderers/counter.vue b/app/javascript/vue/shared/datatable/renderers/counter.vue new file mode 100644 index 000000000..5d5fce4cd --- /dev/null +++ b/app/javascript/vue/shared/datatable/renderers/counter.vue @@ -0,0 +1,47 @@ + + + diff --git a/app/serializers/lists/project_and_folder_serializer.rb b/app/serializers/lists/project_and_folder_serializer.rb index 5d0185c49..48e6c0966 100644 --- a/app/serializers/lists/project_and_folder_serializer.rb +++ b/app/serializers/lists/project_and_folder_serializer.rb @@ -6,17 +6,34 @@ module Lists include Canaid::Helpers::PermissionsHelper include CommentHelper - attributes :name, :code, :created_at, :archived_on, :users, :urls, :folder, :hidden, - :folder_info, :default_public_user_role_id, :team, :top_level_assignable, :supervised_by, - :comments, :updated_at, :permissions, :due_date_cell, :start_on_cell, :description, :status, :favorite, - def team - object.team.name - end + attributes :name, :code, :created_at, :archived_on, :users, :urls, :folder, :hidden, :completed_experiments, :completed_tasks, :total_tasks, + :folder_info, :default_public_user_role_id, :team, :top_level_assignable, :supervised_by, :total_experiments, + :comments, :updated_at, :permissions, :due_date_cell, :start_on_cell, :description, :status, :favorite + + def team + object.team.name + end def folder !project? end + def completed_experiments + object[:completed_experiments_count] + end + + def total_experiments + object[:experiments_count] + end + + def completed_tasks + object[:completed_tasks_count] + end + + def total_tasks + object[:tasks_count] + end + def favorite object.favorite if project? end diff --git a/app/services/lists/experiments_service.rb b/app/services/lists/experiments_service.rb index b48f8bd9b..2e4788bbf 100644 --- a/app/services/lists/experiments_service.rb +++ b/app/services/lists/experiments_service.rb @@ -5,15 +5,21 @@ module Lists private def fetch_records + done_status_id = MyModuleStatusFlow.first.final_status.id @records = @raw_data.joins(:project) .includes(my_modules: { my_module_status: :my_module_status_implications }) .includes(workflowimg_attachment: :blob, user_assignments: %i(user_role user)) .joins('LEFT OUTER JOIN my_modules AS active_tasks ON active_tasks.experiment_id = experiments.id AND active_tasks.archived = FALSE') - .joins('LEFT OUTER JOIN my_modules AS active_completed_tasks ON - active_completed_tasks.experiment_id = experiments.id - AND active_completed_tasks.archived = FALSE AND active_completed_tasks.state = 1') + .joins( + ActiveRecord::Base.sanitize_sql_array([ + 'LEFT OUTER JOIN my_modules AS active_completed_tasks ON + active_completed_tasks.experiment_id = experiments.id + AND active_completed_tasks.archived = FALSE AND active_completed_tasks.my_module_status_id = ?', + done_status_id + ]) + ) .readable_by_user(@user) .with_favorites(@user) .select('experiments.*') @@ -54,21 +60,19 @@ module Lists @records = @records.where('experiments.due_date <= ?', @filters[:due_date_to]) if @filters[:due_date_to].present? - if @filters[:updated_on_from].present? - @records = @records.where('experiments.updated_at > ?', @filters[:updated_on_from]) - end + @records = @records.where('experiments.updated_at > ?', @filters[:updated_on_from]) if @filters[:updated_on_from].present? if @filters[:updated_on_to].present? @records = @records.where('experiments.updated_at < ?', - @filters[:updated_on_to]) + @filters[:updated_on_to]) end if @filters[:archived_on_from].present? @records = @records.where('COALESCE(experiments.archived_on, projects.archived_on) > ?', - @filters[:archived_on_from]) + @filters[:archived_on_from]) end if @filters[:archived_on_to].present? @records = @records.where('COALESCE(experiments.archived_on, projects.archived_on) < ?', - @filters[:archived_on_to]) + @filters[:archived_on_to]) end if @filters[:statuses].present? diff --git a/app/services/lists/projects_service.rb b/app/services/lists/projects_service.rb index 6329f602b..7fab3f2be 100644 --- a/app/services/lists/projects_service.rb +++ b/app/services/lists/projects_service.rb @@ -35,13 +35,35 @@ module Lists private def fetch_projects + done_status_id = MyModuleStatusFlow.first.final_status.id @team.projects .includes(:team, :project_comments, user_assignments: %i(user user_role)) + .joins('LEFT OUTER JOIN experiments AS active_experiments ON + active_experiments.project_id = projects.id + AND active_experiments.archived = FALSE') + .joins('LEFT OUTER JOIN experiments AS active_completed_experiments ON + active_completed_experiments.project_id = projects.id + AND active_completed_experiments.archived = FALSE AND active_completed_experiments.completed_at IS NOT NULL') + .joins('LEFT OUTER JOIN my_modules AS active_tasks ON + active_tasks.experiment_id = active_experiments.id + AND active_tasks.archived = FALSE') + .joins( + ActiveRecord::Base.sanitize_sql_array([ + 'LEFT OUTER JOIN my_modules AS active_completed_tasks ON + active_completed_tasks.experiment_id = active_experiments.id + AND active_completed_tasks.archived = FALSE AND active_completed_tasks.my_module_status_id = ?', + done_status_id + ]) + ) .with_favorites(@user) .visible_to(@user, @team) .left_outer_joins(:project_comments) .select('projects.*') .select('COUNT(DISTINCT comments.id) AS comment_count') + .select('COUNT(DISTINCT active_experiments.id) AS experiments_count') + .select('COUNT(DISTINCT active_completed_experiments.id) AS completed_experiments_count') + .select('COUNT(DISTINCT active_tasks.id) AS tasks_count') + .select('COUNT(DISTINCT active_completed_tasks.id) AS completed_tasks_count') .group('projects.id, favorites.id') end @@ -64,27 +86,20 @@ module Lists search_query = @params[:search].presence || @filters[:query] records = records.where_attributes_like(['projects.name', Project::PREFIXED_ID_SQL, 'projects.description'], search_query) if search_query.present? - if @filters[:members].present? - records = records.joins(:user_assignments).where(user_assignments: { user_id: @filters[:members].values }) - end + records = records.joins(:user_assignments).where(user_assignments: { user_id: @filters[:members].values }) if @filters[:members].present? records = records.where(supervised_by_id: @filters[:head_of_project].values) if @filters[:head_of_project].present? - records = records.where('projects.start_on >= ?', @filters[:start_on_from]) if @filters[:start_on_from].present? + records = records.where(projects: { start_on: (@filters[:start_on_from]).. }) if @filters[:start_on_from].present? - records = records.where('projects.start_on <= ?', @filters[:start_on_to]) if @filters[:start_on_to].present? + records = records.where(projects: { start_on: ..(@filters[:start_on_to]) }) if @filters[:start_on_to].present? - records = records.where('projects.due_date >= ?', @filters[:due_date_from]) if @filters[:due_date_from].present? + records = records.where(projects: { due_date: (@filters[:due_date_from]).. }) if @filters[:due_date_from].present? - records = records.where('projects.due_date <= ?', @filters[:due_date_to]) if @filters[:due_date_to].present? + records = records.where(projects: { due_date: ..(@filters[:due_date_to]) }) if @filters[:due_date_to].present? - if @filters[:archived_on_to].present? - records = records.where('projects.archived_on < ?', - @filters[:archived_on_to]) - end - if @filters[:archived_on_from].present? - records = records.where('projects.archived_on > ?', @filters[:archived_on_from]) - end + records = records.where(projects: { archived_on: ...(@filters[:archived_on_to]) }) if @filters[:archived_on_to].present? + records = records.where('projects.archived_on > ?', @filters[:archived_on_from]) if @filters[:archived_on_from].present? if @filters[:statuses].present? scopes = { diff --git a/config/locales/en.yml b/config/locales/en.yml index 30c1e1063..26f0ba503 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -674,6 +674,8 @@ en: description: 'Description' supervised_by: 'Head of project' status: 'Status' + completed_experiment: "Experiments done" + completed_experiments: "%{completed}/%{all} experiments" end_of_list_placeholder: 'You’ve reached the end of the list' folder: description: "%{projects_count} projects | %{folders_count} folders" diff --git a/spec/services/lists/experiments_service_spec.rb b/spec/services/lists/experiments_service_spec.rb index 62f50fc5e..5b8161aa4 100644 --- a/spec/services/lists/experiments_service_spec.rb +++ b/spec/services/lists/experiments_service_spec.rb @@ -11,6 +11,10 @@ RSpec.describe Lists::ExperimentsService do let(:params) {{ page: 1, per_page: 10, search: '', project: project } } let(:service) { described_class.new(raw_data, params, user:) } + before(:all) do + MyModuleStatusFlow.ensure_default + end + describe '#fetch_records' do context 'when view_mode is archived' do before do diff --git a/spec/services/lists/projects_service_spec.rb b/spec/services/lists/projects_service_spec.rb index 319bb97e5..10b04b4ad 100644 --- a/spec/services/lists/projects_service_spec.rb +++ b/spec/services/lists/projects_service_spec.rb @@ -11,6 +11,10 @@ RSpec.describe Lists::ProjectsService do let(:params) {{ page: 1, per_page: 10, search: '', team: team } } let(:service) { described_class.new(team, user, folder, params) } + before(:all) do + MyModuleStatusFlow.ensure_default + end + describe '#call' do context 'when view_mode is archived' do before do