Experiments table migration [SCI-9800]

This commit is contained in:
Anton 2023-12-06 20:53:11 +01:00
parent 65e9e5abb4
commit 7ffad5b659
28 changed files with 7506 additions and 5884 deletions

View file

@ -22,7 +22,6 @@
}
.projects-show {
--content-header-size: 9em;
.content-header {
height: var(--content-header-size);
}

View file

@ -52,7 +52,7 @@ module Breadcrumbs
@breadcrumbs_items.push(
{
label: project.name,
url: project_path(project, view_mode: archived_branch ? :archived : :active),
url: experiments_path(project_id: project, view_mode: archived_branch ? :archived : :active),
archived: archived_branch
}
)

View file

@ -8,10 +8,10 @@ class ExperimentsController < ApplicationController
include Rails.application.routes.url_helpers
include Breadcrumbs
before_action :load_project, only: %i(new create archive_group restore_group)
before_action :load_project, only: %i(index new create archive_group restore_group)
before_action :load_experiment, except: %i(new create archive_group restore_group
inventory_assigning_experiment_filter actions_toolbar)
before_action :check_read_permissions, except: %i(edit archive clone move new
inventory_assigning_experiment_filter actions_toolbar index)
before_action :check_read_permissions, except: %i(index edit archive clone move new
create archive_group restore_group
inventory_assigning_experiment_filter actions_toolbar)
before_action :check_canvas_read_permissions, only: %i(canvas)
@ -21,12 +21,27 @@ class ExperimentsController < ApplicationController
before_action :check_archive_permissions, only: :archive
before_action :check_clone_permissions, only: %i(clone_modal clone)
before_action :check_move_permissions, only: %i(move_modal move)
before_action :set_inline_name_editing, only: %i(canvas table module_archive)
before_action :set_breadcrumbs_items, only: %i(canvas table module_archive)
before_action :set_navigator, only: %i(canvas module_archive table)
before_action :set_inline_name_editing, only: %i(index canvas table module_archive)
before_action :set_breadcrumbs_items, only: %i(index canvas table module_archive)
before_action :set_navigator, only: %i(index canvas module_archive table)
layout 'fluid'
def index
respond_to do |format|
format.json do
experiments = Lists::ExperimentsService.new(@project.experiments,
params.merge(project: @project),
user: current_user).call
render json: experiments, each_serializer: Lists::ExperimentSerializer, user: current_user,
meta: pagination_dict(experiments)
end
format.html do
render 'experiments/index'
end
end
end
def new
@experiment = Experiment.new
render json: {
@ -44,7 +59,7 @@ class ExperimentsController < ApplicationController
if @experiment.save
experiment_annotation_notification
log_activity(:create_experiment, @experiment)
flash[:success] = t('experiments.create.success_flash',
flash[:success] = t('.success_flash',
experiment: @experiment.name)
render json: { path: my_modules_experiment_url(@experiment) }, status: :ok
@ -164,7 +179,7 @@ class ExperimentsController < ApplicationController
end
format.html do
flash[:success] = t('experiments.update.success_flash', experiment: @experiment.name)
redirect_to project_path(@experiment.project)
redirect_to experiments_path(project_id: @experiment.project)
end
end
else
@ -268,7 +283,7 @@ class ExperimentsController < ApplicationController
else
flash[:error] = t('experiments.clone.error_flash',
experiment: @experiment.name)
redirect_to project_path(@experiment.project)
redirect_to experiments_path(project_id: @experiment.project)
end
end
@ -587,15 +602,27 @@ class ExperimentsController < ApplicationController
end
def set_inline_name_editing
return unless can_manage_experiment?(@experiment)
if @experiment
return unless can_manage_experiment?(@experiment)
@inline_editable_title_config = {
name: 'title',
params_group: 'experiment',
item_id: @experiment.id,
field_to_udpate: 'name',
path_to_update: experiment_path(@experiment)
}
@inline_editable_title_config = {
name: 'title',
params_group: 'experiment',
item_id: @experiment.id,
field_to_udpate: 'name',
path_to_update: experiment_path(@experiment)
}
else
return unless can_manage_project?(@project)
@inline_editable_title_config = {
name: 'title',
params_group: 'project',
item_id: @project.id,
field_to_udpate: 'name',
path_to_update: project_path(@project)
}
end
end
def experiment_annotation_notification(old_text = nil)
@ -677,10 +704,18 @@ class ExperimentsController < ApplicationController
end
def set_navigator
@navigator = {
url: tree_navigator_experiment_path(@experiment),
archived: (action_name == 'module_archive' || params[:view_mode] == 'archived'),
id: @experiment.code
}
@navigator = if @experiment
{
url: tree_navigator_experiment_path(@experiment),
archived: (action_name == 'module_archive' || params[:view_mode] == 'archived'),
id: @experiment.code
}
else
{
url: tree_navigator_project_path(@project),
archived: (action_name == 'index' && params[:view_mode] == 'archived'),
id: @project.code
}
end
end
end

View file

@ -8,7 +8,7 @@ module Navigator
{
id: project.code,
name: project.name,
url: project_path(project, view_mode: archived ? 'archived' : 'active'),
url: experiments_path(project_id: project, view_mode: archived ? 'archived' : 'active'),
archived: project.archived,
type: :project,
has_children: project.has_children,

View file

@ -26,8 +26,8 @@ class ProjectsController < ApplicationController
before_action :load_exp_sort_var, only: :show
before_action :reset_invalid_view_state, only: %i(index cards show)
before_action :set_folder_inline_name_editing, only: %i(index cards)
before_action :set_breadcrumbs_items, only: %i(index show)
before_action :set_navigator, only: %i(index show)
before_action :set_breadcrumbs_items, only: :index
before_action :set_navigator, only: :index
before_action :set_current_projects_view_type, only: %i(index cards)
layout 'fluid'
@ -35,9 +35,12 @@ class ProjectsController < ApplicationController
respond_to do |format|
format.json do
projects = Lists::ProjectsService.new(current_team, current_user, current_folder, params).call
render json: projects, each_serializer: Lists::ProjectAndFolderSerializer, user: current_user, meta: pagination_dict(projects)
render json: projects, each_serializer: Lists::ProjectAndFolderSerializer, user: current_user,
meta: pagination_dict(projects)
end
format.html do
render 'projects/index'
end
format.html do; end
end
end

View file

@ -68,11 +68,15 @@ module GlobalActivitiesHelper
path = repository_path(obj.repository, team: obj.repository.team.id)
when Project
path = obj.archived? ? projects_path : project_path(obj)
path = obj.archived? ? projects_path : experiments_path(project_id: obj)
when Experiment
return current_value unless obj.navigable?
path = obj.archived? ? project_path(obj.project, view_mode: :archived) : my_modules_experiment_path(obj)
path = if obj.archived?
experiments_path(project_id: obj.project, view_mode: :archived)
else
my_modules_experiment_path(obj)
end
when MyModule
return current_value unless obj.navigable?

View file

@ -138,7 +138,7 @@ module MyModulesHelper
{
type: project.class.name.underscore,
value: project.name,
url: project_path(project, view_mode: archived ? 'archived' : 'active'),
url: experiments_path(project_id: project, view_mode: archived ? 'archived' : 'active'),
archived: archived
}
end

View file

@ -0,0 +1,10 @@
import { createApp } from 'vue/dist/vue.esm-bundler.js';
import PerfectScrollbar from 'vue3-perfect-scrollbar';
import ExperimentsList from '../../vue/experiments/list.vue';
import { mountWithTurbolinks } from './helpers/turbolinks.js';
const app = createApp();
app.component('ExperimentsList', ExperimentsList);
app.config.globalProperties.i18n = window.I18n;
app.use(PerfectScrollbar);
mountWithTurbolinks(app, '#ExperimentsList');

View file

@ -0,0 +1,135 @@
<template>
<DataTable
:columnDefs="columnDefs"
tableId="ExperimentsList"
:dataUrl="dataSource"
:reloadingTable="reloadingTable"
:toolbarActions="toolbarActions"
:actionsUrl="actionsUrl"
:withRowMenu="true"
:activePageUrl="activePageUrl"
:archivedPageUrl="archivedPageUrl"
:currentViewMode="currentViewMode"
:filters="filters"
:viewRenders="viewRenders"
@tableReloaded="reloadingTable = false"
>
<template> </template>
</DataTable>
</template>
<script>
import DataTable from '../shared/datatable/table.vue';
import DescriptionRenderer from './renderers/description.vue';
import CompletedTasksRenderer from './renderers/completed_tasks.vue';
import NameRenderer from './renderers/name.vue';
export default {
name: 'ExperimentsList',
components: {
DataTable,
},
props: {
dataSource: { type: String, required: true },
actionsUrl: { type: String, required: true },
activePageUrl: { type: String },
archivedPageUrl: { type: String },
currentViewMode: { type: String, required: true },
},
data() {
return {
reloadingTable: false,
};
},
computed: {
columnDefs() {
const columns = [
{
field: 'name',
flex: 1,
headerName: this.i18n.t('experiments.card.name'),
sortable: true,
cellRenderer: NameRenderer,
},
{
field: 'code',
headerName: this.i18n.t('experiments.id'),
sortable: true,
},
{
field: 'created_at',
headerName: this.i18n.t('experiments.card.start_date'),
sortable: true,
},
{
field: 'updated_at',
headerName: this.i18n.t('experiments.card.modified_date'),
sortable: true,
},
];
if (this.currentViewMode === 'archived') {
columns.push({
field: 'archived_on',
headerName: this.i18n.t('experiments.card.archived_date'),
sortable: true,
});
}
columns.push({
field: 'total_tasks',
headerName: this.i18n.t('experiments.card.completed_task'),
cellRenderer: CompletedTasksRenderer,
sortable: false,
});
columns.push({
field: 'description',
headerName: this.i18n.t('experiments.card.description'),
sortable: false,
cellStyle: { 'white-space': 'normal' },
cellRenderer: DescriptionRenderer,
autoHeight: true,
});
return columns;
},
viewRenders() {
return [{ type: 'table' }, { type: 'cards' }];
},
toolbarActions() {
const left = [];
left.push({
name: 'create',
icon: 'sn-icon sn-icon-new-task',
label: this.i18n.t('experiments.toolbar.new_button'),
type: 'emit',
path: this.createUrl,
buttonStyle: 'btn btn-primary',
});
return {
left,
right: [],
};
},
filters() {
const filters = [
{
key: 'query',
type: 'Text',
},
{
key: 'created_at',
type: 'DateRange',
label: this.i18n.t('filters_modal.created_on.label'),
},
];
return filters;
},
},
methods: {},
};
</script>

View file

@ -0,0 +1,28 @@
<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-48 h-1 bg-sn-light-grey">
<div class="h-full bg-sn-blue" :style="{
width: params.data.completed_tasks / params.data.total_tasks * 100 + '%'
}"></div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'CompletedTasksRenderer',
props: {
params: {
required: true,
},
},
};
</script>

View file

@ -0,0 +1,32 @@
<template>
<div class="group leading-5 relative line-clamp-2 group-hover:marker" :class="{'cursor-pointer': showMoreButton}">
{{ params.data.description }}
<div v-if="showMoreButton" @click.stop="showMore"
class="opacity-0 group-hover:opacity-100 text-xs text-sn-blue absolute bottom-0 right-0
p-0.5 pl-8 bg-gradient-to-r from-transparent from-0% to-sn-super-light-grey to-30%">
{{ i18n.t('experiments.card.more') }}
</div>
</div>
</template>
<script>
export default {
name: 'DescriptionRenderer',
props: {
params: {
required: true,
},
},
computed: {
showMoreButton() {
return this.params.data.description.length > 80;
},
},
methods: {
showMore() {
},
},
};
</script>

View file

@ -0,0 +1,19 @@
<template>
<div class="relative leading-5 h-full flex items-center gap-2 truncate">
<div class="h-10 w-10 p-0.5 bg-sn-light-grey rounded-sm shrink-0">
<img :src="params.data.workflow_img" class="max-h-9 max-w-[36px]">
</div>
<a :href="params.data.urls.show" class="hover:no-underline truncate">{{ params.data.name }}</a>
</div>
</template>
<script>
export default {
name: 'NameRenderer',
props: {
params: {
required: true,
},
},
};
</script>

View file

@ -0,0 +1,46 @@
# frozen_string_literal: true
module Lists
class ExperimentSerializer < ActiveModel::Serializer
include Canaid::Helpers::PermissionsHelper
include Rails.application.routes.url_helpers
attributes :name, :code, :created_at, :updated_at, :workflow_img, :description, :completed_tasks,
:total_tasks, :archived_on, :urls
def created_at
I18n.l(object.created_at, format: :full_date)
end
def updated_at
I18n.l(object.updated_at, format: :full_date)
end
def archived
object.archived_branch?
end
def archived_on
I18n.l(object.archived_on || object.project.archived_on, format: :full_date) if archived
end
def completed_tasks
object[:completed_task_count]
end
def total_tasks
object[:task_count]
end
def urls
urls_list = {}
urls_list[:show] = table_experiment_path(object)
urls_list
end
def workflow_img
rails_blob_path(object.workflowimg, only_path: true) if object.workflowimg.attached?
end
end
end

View file

@ -51,7 +51,7 @@ module Lists
def urls
urls_list = {
show: project? ? project_path(object) : project_folder_path(object),
show: project? ? experiments_path(project_id: object) : project_folder_path(object),
actions: actions_toolbar_projects_path(items: [{ id: object.id,
type: project? ? 'projects' : 'project_folders' }].to_json)
}
@ -59,7 +59,7 @@ module Lists
urls_list[:show] = nil if project? && !can_read_project?(object)
urls_list[:update] = if project?
project_path(object)
experiments_path(project_id: object)
else
project_folder_path(object)
end

View file

@ -243,7 +243,7 @@ module Dashboard
when 'Experiment'
my_modules_experiment_path(object_id)
when 'Project'
project_path(object_id)
experiments_path(project_id: object_id)
when 'Protocol'
protocol_path(Protocol.find(object_id).latest_published_version_or_self.id)
when 'RepositoryBase'

View file

@ -0,0 +1,53 @@
# frozen_string_literal: true
module Lists
class ExperimentsService < BaseService
private
def fetch_records
@raw_data = @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')
.readable_by_user(@user)
.select('experiments.*')
.select('COUNT(DISTINCT active_tasks.id) AS task_count')
.select('COUNT(DISTINCT active_completed_tasks.id) AS completed_task_count')
.group('experiments.id')
view_mode = if @params[:project].archived?
'archived'
else
@params[:view_mode] || 'active'
end
@raw_data = @raw_data.archived if view_mode == 'archived' && @params[:project].active?
@raw_data = @raw_data.active if view_mode == 'active'
@raw_data
end
def filter_records(records)
return records unless @params[:search]
records.where_attributes_like(
['experiments.name', 'experiments.description', "('EX' || experiments.id)"],
@params[:search]
)
end
def sortable_columns
@sortable_columns ||= {
created_at: 'experiments.created_at',
name: 'experiments.name',
code: 'experiments.id',
archived_on: 'COALESCE(experiments.archived_on, projects.archived_on)'
}
end
end
end

View file

@ -11,7 +11,7 @@ module Reports::Docx::DrawProjectHeader
@docx.h1 do
link I18n.t('projects.reports.elements.project_header.title', project: project.name),
scinote_url + Rails.application.routes.url_helpers.project_path(project),
scinote_url + Rails.application.routes.url_helpers.experiments_path(project_id: project),
link_style
end

View file

@ -18,7 +18,9 @@ module SmartAnnotations
def generate_prj_snippet(_, object)
return "<span class='sa-type'>Prj</span>#{object.name} #{I18n.t('atwho.res.archived')}" if object.archived?
"<a class='sa-link' href='#{ROUTES.project_path(object)}'><span class='sa-type'>Prj</span>#{object.name}</a>"
"<a class='sa-link' href='#{ROUTES.experiments_path(project_id: object)}'>
<span class='sa-type'>Prj</span>#{object.name}
</a>"
end
def generate_exp_snippet(_, object)

View file

@ -0,0 +1,15 @@
<% provide(:head_title, t("projects.show.head_title", project: h(@project.name)).html_safe) %>
<div class="content-pane flexible projects-show">
<%= render partial: 'experiments/index/header' %>
<div id="ExperimentsList" class="fixed-content-body">
<experiments-list
actions-url="<%= actions_toolbar_experiments_path %>"
data-source="<%= experiments_path(project_id: @project, format: :json) %>"
active-page-url="<%= experiments_path(project_id: @project, view_mode: :active) %>"
archived-page-url="<%= experiments_path(project_id: @project, view_mode: :archived) %>"
current-view-mode="<%= params[:view_mode] || :active %>"
/>
</div>
<%= javascript_include_tag 'vue_experiments_list' %>
</div>

View file

@ -0,0 +1,21 @@
<div class="content-header sticky-header">
<div class="title-row">
<i class="sn-icon sn-icon-navigator sci--layout--navigator-open cursor-pointer p-1.5 border rounded border-sn-light-grey mr-4"></i>
<h1 class="project-name">
<% if @project.archived? %>
<span class="whitespace-nowrap"><%= t('labels.archived')%></span>&nbsp;
<% end %>
<% if @inline_editable_title_config.present? %>
<%= render partial: "shared/inline_editing",
locals: {
initial_value: @project.name,
config: @inline_editable_title_config
} %>
<% else %>
<div class="name-readonly-placeholder">
<%= @project.name %>
</div>
<% end %>
</h1>
</div>
</div>

View file

@ -3,7 +3,7 @@
<div class="ga-breadcrumb">
<%= image_tag 'icon_small/experiment.svg' %>
<% if subject&.navigable? %>
<% path = subject.archived? ? project_path(subject.project, view_mode: :archived) : my_modules_experiment_path(subject) %>
<% path = subject.archived? ? experiments_path(project_id: subject.project, view_mode: :archived) : my_modules_experiment_path(subject) %>
<%= route_to_other_team(path,
team,
subject.name&.truncate(Constants::NAME_TRUNCATION_LENGTH),

View file

@ -3,7 +3,7 @@
<div class="ga-breadcrumb">
<%= image_tag 'icon_small/project.svg' %>
<% if subject %>
<% path = subject.archived? ? projects_path(team: team) : project_path(subject) %>
<% path = subject.archived? ? projects_path(team: team) : experiments_path(project_id: subject) %>
<%= route_to_other_team(path,
team,
subject.name&.truncate(Constants::NAME_TRUNCATION_LENGTH),

View file

@ -8,7 +8,7 @@
<% else %>
<% if can_read_project?(project) %>
<% if link_to_page == :show %>
<%= route_to_other_team project_path(project),
<%= route_to_other_team experiments_path(project_id: project),
project.team,
text %>
<% else %>

View file

@ -386,7 +386,7 @@ Rails.application.routes.draw do
end
get 'project_folders/:project_folder_id', to: 'projects#index', as: :project_folder_projects
resources :experiments, only: %i(show edit update) do
resources :experiments, only: %i(index show edit update) do
collection do
get 'inventory_assigning_experiment_filter'
get 'edit', action: :edit

View file

@ -49,6 +49,7 @@ const entryList = {
vue_legacy_datetime_picker: './app/javascript/packs/vue/legacy/datetime_picker.js',
vue_label_templates_table: './app/javascript/packs/vue/label_templates_table.js',
vue_projects_list: './app/javascript/packs/vue/projects_list.js',
vue_experiments_list: './app/javascript/packs/vue/experiments_list.js',
vue_design_system_select: './app/javascript/packs/vue/design_system/select.js'
}

View file

@ -15,7 +15,6 @@ Given("I'm on the Protocols result archived page of a {string} task") do |task_n
visit archive_my_module_path(m)
end
# Change methods to steps
# Settings
def visit_profile_page
@ -39,7 +38,7 @@ end
def visit_project_page(project_name)
p = Project.find_by(name: project_name)
visit project_path(p)
visit experiments_path(project_id: p)
end
# Experiment
@ -61,7 +60,6 @@ def inventories_page
visit repositories_path
end
def inventory_page(inventory_name)
i = Repository.find_by(name: inventory_name)
visit repository_path(i)

View file

@ -33,10 +33,10 @@
"@teselagen/ove": "^0.3.23",
"@vue/compiler-sfc": "^3.3.4",
"@vuepic/vue-datepicker": "^7.2.0",
"ag-grid-community": "^30.1.0",
"ag-grid-vue3": "^30.2.1",
"@vueuse/components": "^10.5.0",
"@vueuse/core": "^10.5.0",
"ag-grid-community": "^30.1.0",
"ag-grid-vue3": "^30.2.1",
"ajv": "6.12.6",
"autoprefixer": "10.4.14",
"axios": "^1.6.0",
@ -49,6 +49,7 @@
"croppie": "^2.6.4",
"css-loader": "^6.7.3",
"decimal.js": "^10.3.1",
"eslint-config-airbnb": "^19.0.4",
"extract-text-webpack-plugin": "^3.0.2",
"file-loader": "^6.2.0",
"glob": "^7.1.2",

12896
yarn.lock

File diff suppressed because it is too large Load diff