Merge branch 'release/1.30.0' into develop

This commit is contained in:
Martin Artnik 2024-03-13 15:26:35 +01:00
commit be4f27750c
18 changed files with 145 additions and 93 deletions

View file

@ -51,3 +51,8 @@ html {
opacity: 1;
bottom: 5px;
}
/* Hide caret in Safari */
#relationships-section summary::-webkit-details-marker {
display: none;
}

View file

@ -4,6 +4,10 @@
@apply text-sm font-medium text-sn-dark-grey;
}
.sci-label.error {
@apply text-sn-coral;
}
.sci-input-container-v2 {
@apply relative h-[2.75rem] flex items-center transition-all;
}
@ -24,6 +28,18 @@
@apply !border-sn-coral;
}
.sci-input-container-v2.error::before {
@apply !text-sn-coral;
@apply !text-xs;
bottom: -1rem;
content: attr(data-error-text);
left: 0;
position: absolute;
white-space: nowrap;
width: 100%;
}
.sci-input-container-v2 input:focus {
@apply border-sn-science-blue shadow-none;
}

View file

@ -240,6 +240,16 @@ class RepositoriesController < ApplicationController
render json: @tmp_repository.errors, status: :unprocessable_entity
else
copied_repository = @repository.copy(current_user, @tmp_repository.name)
old_repo_stock_column = @repository.repository_columns.find_by(data_type: 'RepositoryStockValue')
copied_repo_stock_column = copied_repository.repository_columns.find_by(data_type: 'RepositoryStockValue')
if old_repo_stock_column && copied_repo_stock_column
old_repo_stock_column.repository_stock_unit_items.each do |item|
copied_item = item.dup
copied_repo_stock_column.repository_stock_unit_items << copied_item
end
copied_repository.save!
end
if !copied_repository
render json: { name: ['Server error'] }, status: :unprocessable_entity

View file

@ -148,7 +148,7 @@ class RepositoryRowConnectionsController < ApplicationController
name: repository_row.name_with_label,
code: repository_row.code,
path: repository_repository_row_path(repository_row.repository, repository_row),
repository_name: repository_row.repository.name,
repository_name: repository_row.repository.name_with_label,
repository_path: repository_path(repository_row.repository),
unlink_path: repository_repository_row_repository_row_connection_path(
repository_row.repository,

View file

@ -149,17 +149,17 @@ class ResultsController < ApplicationController
def apply_sort!
case params[:sort]
when 'updated_at_asc'
@results = @results.order(updated_at: :asc)
@results = @results.order('results.updated_at' => :asc)
when 'updated_at_desc'
@results = @results.order(updated_at: :desc)
@results = @results.order('results.updated_at' => :desc)
when 'created_at_asc'
@results = @results.order(created_at: :asc)
@results = @results.order('results.created_at' => :asc)
when 'created_at_desc'
@results = @results.order(created_at: :desc)
@results = @results.order('results.created_at' => :desc)
when 'name_asc'
@results = @results.order(name: :asc)
@results = @results.order('results.name' => :asc)
when 'name_desc'
@results = @results.order(name: :desc)
@results = @results.order('results.name' => :desc)
end
end
@ -168,10 +168,10 @@ class ResultsController < ApplicationController
@results = @results.search(current_user, params[:view_mode] == 'archived', params[:query], params[:page] || 1)
end
@results = @results.where('created_at >= ?', params[:created_at_from]) if params[:created_at_from]
@results = @results.where('created_at <= ?', params[:created_at_to]) if params[:created_at_to]
@results = @results.where('updated_at >= ?', params[:updated_at_from]) if params[:updated_at_from]
@results = @results.where('updated_at <= ?', params[:updated_at_to]) if params[:updated_at_to]
@results = @results.where('results.created_at >= ?', params[:created_at_from]) if params[:created_at_from]
@results = @results.where('results.created_at <= ?', params[:created_at_to]) if params[:created_at_to]
@results = @results.where('results.updated_at >= ?', params[:updated_at_from]) if params[:updated_at_from]
@results = @results.where('results.updated_at <= ?', params[:updated_at_to]) if params[:updated_at_to]
end
def load_my_module

View file

@ -75,6 +75,9 @@ export default {
}
},
updateDate(date) {
if (this.date && date && (this.date.setSeconds(0, 0) === date.setSeconds(0, 0))) {
return;
}
this.date = date;
this.updateValue();
},

View file

@ -26,45 +26,42 @@
<form class="flex flex-col gap-6" @submit.prevent novalidate>
<fieldset class="w-full flex justify-between">
<div class="flex flex-col w-40">
<label class="text-sn-grey text-sm font-normal" for="operations">{{ i18n.t('repository_stock_values.manage_modal.operation') }}</label>
<SelectDropdown
<label class="sci-label" for="operations">{{ i18n.t('repository_stock_values.manage_modal.operation') }}</label>
<Select
:disabled="!stockValue?.id"
:value="operation"
:options="operations"
@change="setOperation"
/>
></Select>
</div>
<div class="flex flex-col w-40">
<Input
@update="value => amount = value"
name="stock_amount"
id="stock-amount"
:inputClass="`sci-input-container-v2 ${errors.amount ? 'error' : ''}`"
:labelClass="`text-sm font-normal ${errors.amount ? 'text-sn-delete-red' : 'text-sn-grey'}`"
:type="Number(stockValue.decimals) === 0 ? 'number' : 'text'"
type="number"
:value="amount"
:decimals="stockValue.decimals"
:placeholder="i18n.t('repository_stock_values.manage_modal.amount_placeholder_new')"
required
:label="i18n.t('repository_stock_values.manage_modal.amount')"
showLabel
autoFocus
:required="true"
:min="0"
:error="errors.amount"
/>
</div>
<div class="flex flex-col w-40">
<label :class="`text-sm font-normal ${errors.unit ? 'text-sn-delete-red' : 'text-sn-grey'}`" for="stock-unit">
<label class="sci-label" :class="{ 'error': !!errors.unit }" for="stock-unit">
{{ i18n.t('repository_stock_values.manage_modal.unit') }}
</label>
<SelectDropdown
<Select
:disabled="['add', 'remove'].includes(operation)"
:value="unit"
:options="units"
:placeholder="i18n.t('repository_stock_values.manage_modal.unit_prompt')"
@change="unit = $event"
:className="`${errors.unit ? 'error' : ''}`"
/>
<div class="text-sn-delete-red text-xs" :class="{ visible: errors.unit, invisible: !errors.unit }">
></Select>
<div class="text-sn-coral text-xs" :class="{ visible: errors.unit, invisible: !errors.unit }">
{{ errors.unit }}
</div>
</div>
@ -93,24 +90,21 @@
</div>
<span class="ml-2">{{ i18n.t('repository_stock_values.manage_modal.create_reminder') }}</span>
</div>
<div v-if="reminderEnabled" class="stock-reminder-value flex gap-2 items-center">
<div v-if="reminderEnabled" class="stock-reminder-value flex gap-2 items-center w-40">
<Input
@update="value => lowStockTreshold = value"
name="treshold_amount"
id="treshold-amount"
fieldClass="flex gap-2"
inputClass="sci-input-container-v2 w-40"
labelClass="text-sm font-normal flex items-center"
:type="Number(stockValue.decimals) === 0 ? 'number' : 'text'"
type="number"
:value="lowStockTreshold"
:decimals="stockValue.decimals"
:placeholder="i18n.t('repository_stock_values.manage_modal.amount_placeholder_new')"
required
:required="true"
:label="i18n.t('repository_stock_values.manage_modal.reminder_at')"
showLabel
:min="0"
:error="errors.tresholdAmount"
/>
<span class="text-sm font-normal">
<span class="text-sm font-normal mt-5">
{{ unitLabel }}
</span>
</div>
@ -130,7 +124,7 @@
<button type='button' class='btn btn-secondary' data-dismiss='modal'>
{{ i18n.t('general.cancel') }}
</button>
<button class="btn btn-primary" @click="validateAndsaveStockValue">
<button class="btn btn-primary" @click="validateAndsaveStockValue" :disabled="isSaving">
{{ i18n.t('repository_stock_values.manage_modal.save_stock') }}
</button>
</div>
@ -141,13 +135,13 @@
<script>
import Decimal from 'decimal.js';
import Input from '../shared/legacy/input.vue';
import SelectDropdown from '../shared/select_dropdown.vue';
import Select from '../shared/select.vue';
import Input from '../shared/input.vue';
export default {
name: 'ManageStockValueModal',
components: {
SelectDropdown,
Select,
Input
},
data() {
@ -155,12 +149,13 @@ export default {
operation: null,
operations: [],
stockValue: null,
amount: '',
amount: null,
repositoryRowName: null,
stockUrl: null,
units: null,
unit: null,
reminderEnabled: false,
isSaving: false,
lowStockTreshold: null,
comment: null,
errors: {}
@ -176,7 +171,7 @@ export default {
return unit ? unit[1] : '';
},
newAmount() {
const currentAmount = new Decimal(this.stockValue?.amount || 0);
const currentAmount = this.stockValue?.amount ? new Decimal(this.stockValue?.amount || 0) : null;
const amount = new Decimal(this.amount || 0);
let value;
switch (this.operation) {
@ -226,7 +221,7 @@ export default {
success: (result) => {
this.repositoryRowName = result.repository_row_name;
this.stockValue = result.stock_value;
this.amount = Number(new Decimal(result.stock_value.amount || 0));
this.amount = this.stockValue?.amount && Number(new Decimal(this.stockValue.amount));
this.units = result.stock_value.units;
this.unit = result.stock_value.unit;
this.reminderEnabled = result.stock_value.reminder_enabled;
@ -263,6 +258,7 @@ export default {
this.errors = newErrors;
if (!$.isEmptyObject(newErrors)) return;
this.isSaving = true;
const $this = this;
$.ajax({
@ -282,6 +278,7 @@ export default {
},
success: (result) => {
$this.stockValue = null;
$this.isSaving = false;
$this.closeModal();
$this.closeCallback && $this.closeCallback(result);
}

View file

@ -91,6 +91,9 @@ export default {
},
computed: {
attachmentsOrdered() {
if (this.attachments.some((attachment) => attachment.attributes.uploading)) {
return this.attachments;
}
return this.attachments.sort((a, b) => {
if (a.attributes.asset_order == b.attributes.asset_order) {
switch (this.parent.attributes.assets_order) {

View file

@ -40,8 +40,8 @@
:class="{ 'text-sn-grey font-normal': isBlank, 'whitespace-pre-line py-1': !singleLine }"
@click="enableEdit($event)"
>
<span :class="{'truncate': singleLine }" v-if="smartAnnotation" v-html="sa_value || placeholder" ></span>
<span :class="{'truncate': singleLine}" v-else>{{newValue || placeholder}}</span>
<span :class="{'truncate': singleLine }" :title="sa_value || placeholder" v-if="smartAnnotation" v-html="sa_value || placeholder" ></span>
<span :class="{'truncate': singleLine}" :title="newValue || placeholder" v-else>{{newValue || placeholder}}</span>
</div>
<div

View file

@ -1,75 +1,83 @@
<template>
<div class="relative" :class="fieldClass">
<label v-if="showLabel" :class="labelClass" :for="id">{{ label }}</label>
<div :class="inputClass">
<div class="relative w-full">
<label v-if="label" class="sci-label" :class="{ 'error': !!inputError }" :for="id">{{ label }}</label>
<div class="sci-input-container-v2" :class="{ 'error': !!inputError }" :data-error-text="inputError">
<input ref="input"
:type="type"
lang="en-US"
type="text"
:id="id"
:name="name"
:value="inputValue"
:class="`${error ? 'error' : ''}`"
:value="value"
:class="{ 'error': !!inputError }"
:placeholder="placeholder"
:required="required"
inputmode="numeric"
:min="min"
:max="max"
:step="1/Math.pow(10, decimals)"
:inputmode="(type === 'number') && 'numeric'"
:pattern="pattern"
@input="updateValue"
/>
<div
class="mt-2 text-sn-delete-red whitespace-nowrap truncate text-xs font-normal absolute bottom-[-1rem] w-full"
:title="error"
:class="{ visible: error, invisible: !error}"
>
{{ error }}
</div>
</div>
</div>
</template>
<script>
export default {
name: 'Input',
props: {
id: { type: String, required: false },
fieldClass: { type: String, default: '' },
inputClass: { type: String, default: '' },
labelClass: { type: String, default: '' },
type: { type: String, default: 'text' },
name: { type: String, required: true },
value: { type: [String, Number], required: false },
decimals: { type: [Number, String], default: 0 },
placeholder: { type: String, default: '' },
required: { type: Boolean, default: false },
showLabel: { type: Boolean, default: false },
label: { type: String, required: false },
autoFocus: { type: Boolean, default: false },
error: { type: String, required: false }
error: { type: String, required: false },
min: { type: String },
max: { type: String },
blockInvalidInput: { type: Boolean, default: true }
},
data() {
return {
inputError: false,
lastValue: this.value || ''
};
},
watch: {
value() {
this.lastValue = this.value;
}
},
computed: {
inputValue() {
if (this.type === 'text') return this.value;
pattern() {
if (this.type === 'number' && this.decimals) {
return `[0-9]+([\\.][0-9]{0,${this.decimals}})?`;
} else if (this.type === 'number') {
return '[0-9]+';
}
return isNaN(this.value) ? '' : this.value;
return null;
}
},
methods: {
updateValue($event) {
switch (this.type) {
case 'text':
this.$emit('update', $event.target.value);
break;
case 'number':
const newValue = this.formatDecimalValue($event.target.value);
this.$refs.input.value = newValue;
if (!isNaN(newValue)) this.$emit('update', newValue);
break;
default:
break;
}
this.checkInputError($event);
this.$emit('update', $event.target.value);
},
formatDecimalValue(value) {
const decimalValue = value.replace(/[^-0-9.]/g, '');
if (this.decimals === '0') {
return decimalValue.split('.')[0];
checkInputError() {
const isValid = this.$refs.input.checkValidity();
if (isValid) {
this.lastValue = this.$refs.input.value;
} else if (this.blockInvalidInput) {
this.$refs.input.value = this.lastValue;
return;
}
return decimalValue.match(new RegExp(`^-?\\d*(\\.\\d{0,${this.decimals}})?`))[0];
this.inputError = this.error || (!isValid && this.i18n.t('input.errors.invalid_value'));
}
}
};

View file

@ -349,15 +349,15 @@ class MyModule < ApplicationRecord
rows.find_each do |row|
row_json = []
row_json << row.code
row_json << (row.archived ? "#{row.name} [#{I18n.t('general.archived')}]" : row.name)
row_json << (row.archived ? "#{escape_script_tag(row.name)} [#{I18n.t('general.archived')}]" : escape_script_tag(row.name))
row_json << I18n.l(row.created_at, format: :full)
row_json << row.created_by.full_name
row_json << escape_script_tag(row.created_by.full_name)
if repository.has_stock_management? && repository.has_stock_consumption?
if repository.is_a?(RepositorySnapshot)
consumed_stock = row.repository_stock_consumption_cell&.value&.formatted
consumed_stock = escape_script_tag(row.repository_stock_consumption_cell&.value&.formatted)
row_json << (consumed_stock || 0)
else
row_json << row.row_consumption(row.stock_consumption)
row_json << escape_script_tag(row.row_consumption(row.stock_consumption))
end
end
data << row_json
@ -488,6 +488,10 @@ class MyModule < ApplicationRecord
protocols << Protocol.new_blank_for_module(self)
end
def escape_script_tag(value)
value&.gsub(/\</, '&lt;')&.gsub(/\>/, '&gt;')
end
def coordinates_uniqueness_check
if experiment && experiment.my_modules.active.where(x: x, y: y).where.not(id: id).any?
errors.add(:position, I18n.t('activerecord.errors.models.my_module.attributes.position.not_unique'))

View file

@ -26,8 +26,8 @@
</div>
</div>
<div class="report-element-body">
<input type="hidden" class="hot-table-contents" value="<%= table.contents_utf_8 %>" />
<input type="hidden" class="hot-table-metadata" value="<%= table.metadata.to_json %>" />
<input type="hidden" class="hot-table-contents" value="<%= table.contents_utf_8.gsub(/\</, '&lt;').gsub(/\>/, '&gt;') %>" />
<input type="hidden" class="hot-table-metadata" value="<%= table.metadata.to_json.gsub(/\</, '&lt;').gsub(/\>/, '&gt;') %>" />
<div class="hot-table-container"></div>
<table class="report-common-table-format"></table>
</div>

View file

@ -23,8 +23,8 @@
</div>
</div>
<div class="report-element-body">
<input type="hidden" class="hot-table-contents" value="<%= table.contents_utf_8 %>" />
<input type="hidden" class="hot-table-metadata" value="<%= table.metadata.to_json %>" />
<input type="hidden" class="hot-table-contents" value="<%= table.contents_utf_8.gsub(/\</, '&lt;').gsub(/\>/, '&gt;') %>" />
<input type="hidden" class="hot-table-metadata" value="<%= table.metadata.to_json.gsub(/\</, '&lt;').gsub(/\>/, '&gt;') %>" />
<div class="hot-table-container"></div>
<table class="report-common-table-format"></table>
</div>

View file

@ -22,7 +22,7 @@
</div>
</div>
<ul class="experiment-contents collapse in" id="experimentContentContainer<%= experiment.id %>">
<% experiment.my_modules.viewable_by_user(current_user, current_team).active.sort_by { |m| @project_contents[:my_modules].index(m.id) || @project_contents[:my_modules].length if report }.each do |my_module| %>
<% experiment.my_modules.viewable_by_user(current_user, current_team).active.order(id: :asc).each do |my_module| %>
<li class="experiment-my-module">
<span class="sci-checkbox-container">
<input type="checkbox" value="<%= my_module.id %>" class="sci-checkbox report-my-module-checkbox"

View file

@ -16,10 +16,12 @@
<div class="form-group sci-input-container">
<label><%= t("repositories.index.modal_copy.name") %></label>
<%= f.text_field :name,
help: t("repositories.index.modal_copy.description"),
autofocus: true,
class: "sci-input-field",
placeholder: t("repositories.index.modal_copy.name_placeholder") %>
<div class="text-red-500">
<%= t("repositories.index.modal_copy.description") %>
</div>
</div>
</div>
</div>

View file

@ -12,5 +12,5 @@
data: { min: 0, max: Constants::REPOSITORY_NUMBER_TYPE_MAX_DECIMALS }) %>
</div>
<%= javascript_include_tag 'repository_columns/manage_column_partials/number' %>
<%= javascript_include_tag 'repository_columns/manage_column_partials/number', nonce: true %>
</div>

View file

@ -13,7 +13,7 @@
class: 'form-control stock-column',
data: { min: 0, max: Constants::REPOSITORY_NUMBER_TYPE_MAX_DECIMALS }) %>
<%= javascript_include_tag 'repository_columns/manage_column_partials/stock' %>
<%= javascript_include_tag 'repository_columns/manage_column_partials/stock', nonce: true %>
</div>
</div>
<div class="form-group">

View file

@ -4086,6 +4086,10 @@ en:
From: 'From'
To: 'To'
input:
errors:
invalid_value: 'Invalid value'
marvinjs:
new_sketch: "New chemical drawing"
new_button: "New chemical drawing"