mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-11-08 07:21:03 +08:00
Add experiments and tasks counter to project table [SCI-11944]
This commit is contained in:
parent
23e58a2fc2
commit
2e8152abc4
11 changed files with 203 additions and 61 deletions
|
|
@ -1,43 +1,24 @@
|
|||
<template>
|
||||
<div class="relative leading-5 h-full flex items-center">
|
||||
<div>
|
||||
{{ i18n.t('experiments.card.completed_value', {
|
||||
completed: params.data.completed_tasks,
|
||||
all: params.data.total_tasks
|
||||
}) }}
|
||||
<div class="py-1">
|
||||
<div class="w-24 h-1 bg-sn-light-grey">
|
||||
<div class="h-full"
|
||||
:class="{
|
||||
'bg-sn-black': params.data.archived_on,
|
||||
'bg-sn-blue': !params.data.archived_on
|
||||
}"
|
||||
:style="{
|
||||
width: `${progress}%`
|
||||
}"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<CounterRenderer
|
||||
:params="params"
|
||||
totalField="total_tasks"
|
||||
completedField="completed_tasks"
|
||||
label="experiments.card.completed_value"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CounterRenderer from '../../shared/datatable/renderers/counter.vue';
|
||||
|
||||
export default {
|
||||
name: 'CompletedTasksRenderer',
|
||||
props: {
|
||||
params: {
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
progress() {
|
||||
const { completed_tasks: completedTasks, total_tasks: totalTasks } = this.params.data;
|
||||
|
||||
if (totalTasks === 0) return 3;
|
||||
if (completedTasks === 0) return 3;
|
||||
|
||||
return (completedTasks / totalTasks) * 100;
|
||||
required: true
|
||||
}
|
||||
},
|
||||
components: {
|
||||
CounterRenderer
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -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'),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
<template>
|
||||
<CounterRenderer
|
||||
v-if="!params.data.folder"
|
||||
:params="params"
|
||||
totalField="total_experiments"
|
||||
completedField="completed_experiments"
|
||||
label="projects.index.card.completed_experiments"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CounterRenderer from '../../shared/datatable/renderers/counter.vue';
|
||||
|
||||
export default {
|
||||
name: 'CompletedExperimentsRenderer',
|
||||
props: {
|
||||
params: {
|
||||
required: true
|
||||
}
|
||||
},
|
||||
components: {
|
||||
CounterRenderer
|
||||
}
|
||||
};
|
||||
</script>
|
||||
25
app/javascript/vue/projects/renderers/completed_tasks.vue
Normal file
25
app/javascript/vue/projects/renderers/completed_tasks.vue
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
<template>
|
||||
<CounterRenderer
|
||||
v-if="!params.data.folder"
|
||||
:params="params"
|
||||
totalField="total_tasks"
|
||||
completedField="completed_tasks"
|
||||
label="experiments.card.completed_value"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CounterRenderer from '../../shared/datatable/renderers/counter.vue';
|
||||
|
||||
export default {
|
||||
name: 'CompletedTasksRenderer',
|
||||
props: {
|
||||
params: {
|
||||
required: true
|
||||
}
|
||||
},
|
||||
components: {
|
||||
CounterRenderer
|
||||
}
|
||||
};
|
||||
</script>
|
||||
47
app/javascript/vue/shared/datatable/renderers/counter.vue
Normal file
47
app/javascript/vue/shared/datatable/renderers/counter.vue
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
<template>
|
||||
<div class="relative leading-5 h-full flex items-center">
|
||||
<div>
|
||||
{{ i18n.t(label, {
|
||||
completed: this.params.data[this.completedField],
|
||||
all: this.params.data[this.totalField]
|
||||
}) }}
|
||||
<div class="py-1">
|
||||
<div class="w-24 h-1 bg-sn-light-grey">
|
||||
<div class="h-full"
|
||||
:class="{
|
||||
'bg-sn-black': params.data.archived_on,
|
||||
'bg-sn-blue': !params.data.archived_on
|
||||
}"
|
||||
:style="{
|
||||
width: `${progress}%`
|
||||
}"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'CounterRenderer',
|
||||
props: {
|
||||
params: {
|
||||
required: true
|
||||
},
|
||||
totalField: String,
|
||||
completedField: String,
|
||||
label: String
|
||||
},
|
||||
computed: {
|
||||
progress() {
|
||||
const completedCounter = this.params.data[this.completedField];
|
||||
const totalCounter = this.params.data[this.totalField];
|
||||
|
||||
if (totalCounter === 0) return 3;
|
||||
if (completedCounter === 0) return 3;
|
||||
|
||||
return (completedCounter / totalCounter) * 100;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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?
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue