Skip to content

Commit

Permalink
Enhance versioning + revert function
Browse files Browse the repository at this point in the history
Make revert function work (#780)

- Update version list after reverting a change

- Fix issue /w big svgs

- Make reverting molfile / svg work

- Fix merge issues

- Replace passing local variables /w using state

Add missing versioning for Screens, Wellplates, and Research plans

Change the way versions are displayed in the version table. Previously
we had one version per database update. Now we group versions by the
request id. These changes are now merged to make it more intuitive for
the user.

Implement revert functionality /w fetchers, reverters, and serializers

Implement a new revert UI with one button per version and one modal to
select revertible values. Delete old revert button.

Co-authored-by: Martin Schneider <[email protected]>
Co-authored-by: VadimKeller <[email protected]>
  • Loading branch information
3 people authored and Adrian Herrmann committed Sep 19, 2024
1 parent 68e51f5 commit ea20c95
Show file tree
Hide file tree
Showing 73 changed files with 2,768 additions and 749 deletions.
136 changes: 54 additions & 82 deletions app/api/chemotion/version_api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,39 +17,8 @@ class VersionAPI < Grape::API

route_param :id do
get do
# find specific sample and load only required data
sample = Sample.select(:id, :name, :log_data, :updated_at).find(params[:id])

analyses = sample.analyses.flat_map { |analysis| analysis.self_and_descendants.select(:id, :name, :updated_at, :log_data) }

# create cache key for sample
timestamp = [
sample.updated_at,
analyses.map(&:updated_at).max,
Attachment.where(attachable_id: analyses.map(&:id), attachable_type: 'Container').maximum(:updated_at)
].reject(&:nil?).max.to_i
cache_key = "versions/samples/#{sample.id}/#{timestamp}"

# cache processed and sorted versions to speed up pagination
versions = Rails.cache.fetch cache_key do
all_versions = sample.versions_hash
all_versions += sample.residues.select(:sample_id, :log_data).flat_map do |residue|
residue.versions_hash(sample.name)
end
all_versions += sample.elemental_compositions.select(:sample_id, :log_data).flat_map do |elemental_composition|
elemental_composition.versions_hash(sample.name)
end

analyses.each do |analysis|
all_versions += analysis.versions_hash
all_versions += analysis.attachments.select(:attachable_id, :attachable_type, :filename, :log_data).flat_map do |attachment|
attachment.versions_hash(attachment.filename)
end
end

all_versions.sort_by! { |version| -version['t'].to_i } # sort versions with the latest changes in the first place
.each_with_index { |record, index| record['v'] = all_versions.length - index } # adjust v to be uniq and in right order
end
sample = Sample.with_log_data.find(params[:id])
versions = Versioning::Fetcher.call(sample)

{ versions: paginate(Kaminari.paginate_array(versions)) }
end
Expand All @@ -67,60 +36,63 @@ class VersionAPI < Grape::API

route_param :id do
get do
# find specific sample and load only required data
reaction = Reaction.select(:id, :name, :log_data, :updated_at).find(params[:id])

analyses = (
reaction.analyses +
reaction.samples.includes(:container).pluck('containers.id').flat_map { |container_id| Container.analyses_for_root(container_id) }
).flat_map { |analysis| analysis.self_and_descendants.select(:id, :name, :updated_at, :log_data) }

# create cache key for reaction
timestamp = [
reaction.updated_at,
reaction.samples.with_deleted.maximum(:updated_at),
reaction.reactions_samples.with_deleted.maximum(:updated_at),
analyses.map(&:updated_at).max,
Attachment.where(attachable_id: analyses.map(&:id), attachable_type: 'Container').maximum(:updated_at)
].reject(&:nil?).max.to_i
cache_key = "versions/reactions/#{reaction.id}/#{timestamp}"

# cache processed and sorted versions of all reaction dependent records and merge them into one list to speed up pagination
versions = Rails.cache.fetch cache_key do
all_versions = reaction.versions_hash

analyses.each do |analysis|
all_versions += analysis.versions_hash
all_versions += analysis.attachments.select(:attachable_id, :attachable_type, :filename, :log_data).flat_map do |attachment|
attachment.versions_hash(attachment.filename)
end
end

samples = reaction.samples.with_deleted.select('samples.id, samples.name, samples.log_data')
samples.each do |sample|
all_versions += sample.versions_hash
all_versions += sample.residues.select(:sample_id, :log_data).flat_map do |residue|
residue.versions_hash(sample.name)
end
all_versions += sample.elemental_compositions.select(:sample_id, :log_data).flat_map do |elemental_composition|
elemental_composition.versions_hash(sample.name)
end
end

reactions_samples = reaction.reactions_samples.with_deleted.select(:sample_id, :log_data, :type)
all_versions += reactions_samples.flat_map do |reactions_sample|
sample = samples.detect { |s| s.id == reactions_sample.sample_id }
reactions_sample.versions_hash(sample.name)
end

all_versions.sort_by! { |version| -version['t'].to_i } # sort versions with the latest changes in the first place
.each_with_index { |record, index| record['v'] = all_versions.length - index } # adjust v to be uniq and in right order
end
reaction = Reaction.with_log_data.find(params[:id])
versions = Versioning::Fetcher.call(reaction)

{ versions: paginate(Kaminari.paginate_array(versions)) }
end
end
end

resource :research_plans do
desc 'Return versions of the given research plan'

params do
requires :id, type: Integer, desc: 'Research plan id'
end

paginate per_page: 10, offset: 0, max_per_page: 100

route_param :id do
get do
research_plan = ResearchPlan.with_log_data.find(params[:id])
versions = Versioning::Fetcher.call(research_plan)

{ versions: paginate(Kaminari.paginate_array(versions)) }
end
end
end

resource :screens do
desc 'Return versions of the given screen'

params do
requires :id, type: Integer, desc: 'Screen id'
end

paginate per_page: 10, offset: 0, max_per_page: 100

route_param :id do
get do
screen = Screen.with_log_data.find(params[:id])
versions = Versioning::Fetcher.call(screen)

{ versions: paginate(Kaminari.paginate_array(versions)) }
end
end
end

resource :revert do
desc 'Revert selected changes'

params do
requires :changes, type: JSON, desc: 'Changes hash'
end

post do
Versioning::Reverter.call(params[:changes])
end
end
end
end
end
3 changes: 3 additions & 0 deletions app/assets/stylesheets/components/svg_with_popver.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.image-with-full-width {
width: 100%;
}
126 changes: 126 additions & 0 deletions app/assets/stylesheets/history.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
.history-legend {
display: flex;
justify-content: flex-end;
list-style: none;
padding: 0;
margin: 2px 2px 10px 0;

&__item {
font-size: 10px;
padding-left: .5em;

&::before {
content: "\f0c8";
font-family: FontAwesome;
display: inline-block;
padding: 0 0.2em;
}

&--old {
&::before {
color: #f2dede;
}
}

&--new {
&::before {
color: #dff0d8;
}
}

&--current {
&::before {
color: #f5f5f5;
}
}
}
}

.history-modal .modal-dialog {
top: 0;
transform: translate(-50%, 0) !important;
}

.history-alert {
margin-bottom: 0;
margin-top: 20px;
}

.history-checkbox-label {
margin-bottom: 0;
display: flex;
gap: 5px;
cursor: pointer;

input {
margin-top: 0 !important;
}
}

.history-table {
&__caret {
transition: transform .1s ease-out;
}

&__row {
cursor: pointer;

&.active {
.history-table__caret {
transform: rotate(90deg);
}
}
}

&-breadcrumb {
font-size: 13px;
padding: 8px 15px;
margin: 0;
list-style: none;
background-color: #f5f5f5;
border-radius: 4px;

&:not(:first-child) {
margin-top: 10px;
}

&__element {
display: inline-block;
}

&__element + &__element:before {
padding: 0 5px;
color: #ccc;
content: "/ ";
}
}

.bg-current {
background-color: #f5f5f5;
}

.row-change {
display: flex;
flex-wrap: wrap;
font-size: 10px;
margin: 10px 0 0;
word-break: break-all;

& > [class*="col-"] {
padding: 6px;
}

.ql-container {
font-size: 10px;
}

.ql-editor {
padding: 0;
}

.ql-tooltip.ql-hidden {
height: 0;
padding-top: 10px;
}
}
}
24 changes: 0 additions & 24 deletions app/assets/stylesheets/version.scss

This file was deleted.

3 changes: 1 addition & 2 deletions app/models/attachment.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,7 @@
#

class Attachment < ApplicationRecord # rubocop:disable Metrics/ClassLength
include Versionable

has_logidze
include AttachmentJcampAasm
include AttachmentJcampProcess
include Labimotion::AttachmentConverter
Expand Down
Loading

0 comments on commit ea20c95

Please sign in to comment.