Merge pull request #8379 from artoscinote/ma_SCI_11736

Implement repository row field value snapshot view in sidebar [SCI-11736]
This commit is contained in:
Martin Artnik 2025-04-01 14:07:26 +02:00 committed by GitHub
commit 6e34ca408c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 77 additions and 42 deletions

View file

@ -385,7 +385,15 @@ class RepositoryRowsController < ApplicationController
end
def load_repository_row
@repository_row = @repository.repository_rows.eager_load(:repository_columns).find_by(id: params[:id])
@repository_row =
if params[:form_repository_rows_field_value_id]
FormRepositoryRowsFieldValue
.find_by(id: params[:form_repository_rows_field_value_id])
&.reified_repository_row_by_id(params[:id])
else
@repository.repository_rows.eager_load(:repository_columns).find_by(id: params[:id])
end
render_404 unless @repository_row
end

View file

@ -63,7 +63,7 @@ export default {
},
methods: {
itemCardUrl(row) {
return repository_repository_row_path(row.repository_id, row.id);
return repository_repository_row_path(row.repository_id, row.id, { form_repository_rows_field_value_id: this.field.field_value.id });
},
addValue(rows) {
const rowIds = this.assignedIds;

View file

@ -10,7 +10,7 @@
<div id="repository-item-sidebar" data-e2e="e2e-CO-itemCard" class="w-full h-full pl-6 bg-white flex flex-col">
<div ref="stickyHeaderRef" id="sticky-header-wrapper"
class="sticky top-0 right-0 bg-white flex z-50 flex-col h-[78px] pt-6">
class="sticky top-0 right-0 bg-white flex z-50 flex-col pt-6">
<div class="header flex w-full h-[30px] pr-6">
<repository-item-sidebar-title v-if="defaultColumns"
:editable="permissions.can_manage && !defaultColumns?.archived"
@ -22,6 +22,9 @@
<i id="close-icon" data-e2e="e2e-BT-itemCard-close" @click="toggleShowHideSidebar(null)"
class="sn-icon sn-icon-close ml-auto cursor-pointer my-auto mx-0"></i>
</div>
<div v-if="snapshotAt" class="text-sn-grey mt-2">
{{ i18n.t('repositories.item_card.snapshot_label', { timestamp: snapshotAt, name: snapshotByName }) }}
</div>
<div id="divider" class="bg-sn-light-grey flex items-center self-stretch h-px mt-5 mr-6"></div>
</div>
@ -466,7 +469,9 @@ export default {
relationshipDetailsState: {},
selectedToUnlink: null,
initialSectionId: null,
loadingError: false
loadingError: false,
snapshotAt: null,
snapshotByName: null
};
},
provide() {
@ -624,6 +629,8 @@ export default {
this.icons = result.icons;
this.dataLoading = false;
this.notification = result.notification;
this.snapshotByName = result.snapshot_by_name;
this.snapshotAt = result.snapshot_at;
this.$nextTick(() => {
this.generateBarCode(this.defaultColumns.code);

View file

@ -13,35 +13,18 @@
}}
</div>
</div>
<div v-if="canEdit" class="w-full contents">
<text-area :initialValue="colVal?.edit"
:noContentPlaceholder="i18n.t('repositories.item_card.repository_text_value.placeholder')"
:placeholder="i18n.t('repositories.item_card.repository_text_value.placeholder')"
:unEditableRef="`textRef`"
:smartAnnotation="true"
:expandable="expandable"
:collapsed="collapsed"
@toggleExpandableState="toggleExpandableState"
@update="update"
className="px-3"
:data-e2e="'e2e-IF-repoItemSBcustomColumns-input' + colId"
/>
</div>
<div v-else-if="colVal?.view"
ref="textRef"
v-html="colVal?.view"
class="text-sn-dark-grey box-content text-sm font-normal leading-5
overflow-y-auto pr-3 rounded w-[calc(100%-2rem)]]"
:class="{
'max-h-[4rem]': collapsed,
'max-h-[40rem]': !collapsed
}"
>
</div>
<div v-else class="text-sn-dark-grey font-inter text-sm font-normal leading-5 pr-3 py-2 w-[calc(100%-2rem)]]">
{{ i18n.t("repositories.item_card.repository_text_value.no_text") }}
</div>
<text-area :initialValue="colVal?.edit"
:noContentPlaceholder="i18n.t('repositories.item_card.repository_text_value.placeholder')"
:placeholder="i18n.t('repositories.item_card.repository_text_value.placeholder')"
:unEditableRef="`textRef`"
:smartAnnotation="true"
:expandable="expandable"
:collapsed="collapsed"
@toggleExpandableState="toggleExpandableState"
@update="update"
className="px-3"
:data-e2e="'e2e-IF-repoItemSBcustomColumns-input' + colId"
/>
</div>
</template>

View file

@ -166,12 +166,12 @@ export default {
if (this.formFieldValues.find((formFieldValue) => formFieldValue.form_field_id === formFieldId)) {
this.formFieldValues = this.formFieldValues.map((formFieldValue) => {
if (formFieldValue.form_field_id === formFieldId) {
return response.data.data.attributes;
return { ...response.data.data.attributes, id: response.data.data.id };
}
return formFieldValue;
});
} else {
this.formFieldValues.push(response.data.data.attributes);
this.formFieldValues.push({ ...response.data.data.attributes, id: response.data.data.id });
}
});
},

View file

@ -115,6 +115,8 @@ export default {
});
},
enableEdit(e) {
if (!this.canEdit) return;
if (e && $(e.target).hasClass('atwho-user-popover')) return;
if (e && $(e.target).hasClass('sa-name')) return;
if (e && $(e.target).hasClass('sa-link')) return;

View file

@ -25,6 +25,35 @@ class FormRepositoryRowsFieldValue < FormFieldValue
data
end
def reified_repository_row_by_id(repository_row_id)
# build an in-memory, read-only representation of the repository row in the snapshot
row_attributes = data.find { |r| r['id'] == repository_row_id.to_i }
row = RepositoryRow.new(row_attributes.except('repository_cells'))
row.repository_cells = row_attributes['repository_cells'].map do |cell_attributes|
repository_cell = row.repository_cells.build(cell_attributes.except('value', 'repository_column'))
repository_column = RepositoryColumn.new(cell_attributes['repository_column'])
repository_column.readonly!
repository_cell.assign_attributes(
repository_column_id: cell_attributes['repository_column']['id'],
repository_row_id: repository_row_id.to_i,
repository_column: repository_column,
value:
cell_attributes['repository_column']['data_type'].constantize.new(
cell_attributes['value'].except('repository_column')
)
)
repository_cell.readonly!
repository_cell
end
row.snapshot_by_name = User.find_by(id: row.snapshot_by_id)&.full_name
row.readonly!
row
end
def formatted
value&.map { |i| "#{i['name']} (#{RepositoryRow::ID_PREFIX}#{i['id']})" }&.join(' | ')
end

View file

@ -112,7 +112,7 @@ class RepositoryRow < ApplicationRecord
length: { maximum: Constants::NAME_MAX_LENGTH }
validates :created_by, presence: true
attr_accessor :import_status, :import_message
attr_accessor :import_status, :import_message, :snapshot_at, :snapshot_by_id, :snapshot_by_name
scope :active, -> { where(archived: false) }
scope :archived, -> { where(archived: true) }

View file

@ -3,7 +3,7 @@
class FormFieldValueSerializer < ActiveModel::Serializer
include Canaid::Helpers::PermissionsHelper
attributes :form_field_id, :type, :value, :submitted_at, :submitted_by_full_name,
attributes :id, :form_field_id, :type, :value, :submitted_at, :submitted_by_full_name,
:unit, :not_applicable, :selection, :datetime, :datetime_to
def submitted_by_full_name

View file

@ -1,10 +1,15 @@
# frozen_string_literal: true
json.id @repository_row.id
if @repository_row.snapshot_at
json.snapshot_at DateTime.parse(@repository_row.snapshot_at).strftime("#{I18n.backend.date_format} %H:%M")
json.snapshot_by_name @repository_row.snapshot_by_name
end
json.repository do
json.id @repository.id
json.name @repository.name
json.is_snapshot @repository.is_a?(RepositorySnapshot)
json.is_snapshot @repository.is_a?(RepositorySnapshot) || !@repository_row.snapshot_at.nil?
end
json.editable @repository_row.editable?
json.notification @notification
@ -12,9 +17,9 @@ json.notification @notification
json.update_path update_cell_repository_repository_row_path(@repository, @repository_row)
json.permissions do
json.can_export_repository_stock can_export_repository_stock?(@repository)
json.can_manage can_manage_repository_rows?(@repository) if @repository.is_a?(Repository) && !@repository.is_a?(SoftLockedRepository)
json.can_connect_rows can_connect_repository_rows?(@repository) if @repository.is_a?(Repository) && !@repository.is_a?(SoftLockedRepository)
json.can_export_repository_stock @repository_row.snapshot_at.nil? && can_export_repository_stock?(@repository)
json.can_manage @repository_row.snapshot_at.nil? && can_manage_repository_rows?(@repository) if @repository.is_a?(Repository) && !@repository.is_a?(SoftLockedRepository)
json.can_connect_rows @repository_row.snapshot_at.nil? && can_connect_repository_rows?(@repository) if @repository.is_a?(Repository) && !@repository.is_a?(SoftLockedRepository)
end
json.actions do
@ -106,7 +111,7 @@ end
json.custom_columns do
json.array! repository_columns_ordered_by_state(@repository_row.repository).each do |repository_column|
repository_cell = @repository_row.repository_cells.find_by(repository_column: repository_column)
repository_cell = @repository_row.repository_cells.find { |c| c.repository_column_id == repository_column.id }
options = case repository_column.data_type
when 'RepositoryListValue'

View file

@ -2714,6 +2714,7 @@ en:
one: "day"
other: "days"
stock_export: "Export"
snapshot_label: "Snapshot on %{timestamp} by %{name}"
custom_columns_label: "Custom columns"
no_custom_columns_label: "No custom columns"
repository_time_range_value: