Merge branch 'develop' into features/native-tables-revamp

This commit is contained in:
Martin Artnik 2025-02-13 14:33:19 +01:00
commit ace4139a84
35 changed files with 286 additions and 109 deletions

View file

@ -1 +1 @@
1.39.0
1.39.1.1

View file

@ -81,16 +81,19 @@
dropdownSelector.init(myModuleUserSelector, {
closeOnSelect: true,
labelHTML: true,
tagClass: 'my-module-user-tags',
tagLabel: (data) => {
return `<img class="img-responsive block-inline" src="${data.params.avatar_url}" alt="${data.label}"/>
<span class="user-full-name block-inline">${data.label}</span>`;
return `<div class="flex items-center gap-2">
<img class="img-responsive block-inline" src="${data.params.avatar_url}" alt="${data.label}"/>
<span class="user-full-name block-inline">${data.label}</span>
</div>`;
},
optionLabel: (data) => {
if (data.params.avatar_url) {
return `<span class="global-avatar-container" style="margin-top: 10px">
<img src="${data.params.avatar_url}" alt="${data.label}"/></span>
<span style="margin-left: 10px">${data.label}</span>`;
return `<div class="flex items-center gap-2">
<span class="global-avatar-container">
<img src="${data.params.avatar_url}" alt="${data.label}"/></span>
<span style="margin-left: 10px">${data.label}</span>
</div>`;
}
return data.label;

View file

@ -747,7 +747,7 @@ var MyModuleRepositories = (function() {
function openUpdateRecordsModal(downstream) {
var updateUrl = FULL_VIEW_MODAL.data('update-url-modal');
$.get(updateUrl, { selected_rows: SELECTED_ROWS, downstream: downstream }, function(data) {
$.post(updateUrl, { selected_rows: SELECTED_ROWS, downstream: downstream }, function(data) {
var assignList;
var assignListScrollbar;
var unassignList;
@ -766,7 +766,7 @@ var MyModuleRepositories = (function() {
function openAssignRecordsModal(downstream) {
var assignUrl = FULL_VIEW_MODAL.data('assign-url-modal');
$.get(assignUrl, { selected_rows: Object.keys(SELECTED_ROWS), downstream: downstream }, function(data) {
$.post(assignUrl, { selected_rows: Object.keys(SELECTED_ROWS), downstream: downstream }, function(data) {
UPDATE_REPOSITORY_MODAL.find('.modal-content').html(data.html);
UPDATE_REPOSITORY_MODAL.data('update-url', data.update_url);
UPDATE_REPOSITORY_MODAL.modal('show');

View file

@ -1,24 +0,0 @@
const GLOBAL_CONSTANTS = {
NAME_TRUNCATION_LENGTH: <%= Constants::NAME_TRUNCATION_LENGTH %>,
NAME_MAX_LENGTH: <%= Constants::NAME_MAX_LENGTH %>,
NAME_MIN_LENGTH: <%= Constants::NAME_MIN_LENGTH %>,
TEXT_MAX_LENGTH: <%= Constants::TEXT_MAX_LENGTH %>,
TABLE_CARD_MIN_WIDTH: 340, <%# pixels %>
TABLE_CARD_GAP: 16, <%# pixels %>
FILENAME_TRUNCATION_LENGTH: <%= Constants::FILENAME_TRUNCATION_LENGTH %>,
FILE_MAX_SIZE_MB: parseInt($('meta[name="max-file-size"]').attr('content'), 10),
IS_SAFARI: /^((?!chrome|android).)*safari/i.test(navigator.userAgent),
REPOSITORY_LIST_ITEMS_PER_COLUMN: <%= Constants::REPOSITORY_LIST_ITEMS_PER_COLUMN %>,
REPOSITORY_CHECKLIST_ITEMS_PER_COLUMN: <%= Constants::REPOSITORY_CHECKLIST_ITEMS_PER_COLUMN %>,
REPOSITORY_STOCK_UNIT_ITEMS_PER_COLUMN: <%= Constants::REPOSITORY_STOCK_UNIT_ITEMS_PER_COLUMN %>,
HAS_UNSAVED_DATA_CLASS_NAME: 'has-unsaved-data',
DEFAULT_ELEMENTS_PER_PAGE: <%= Constants::DEFAULT_ELEMENTS_PER_PAGE %>,
FILENAME_MAX_LENGTH: <%= Constants::FILENAME_MAX_LENGTH %>,
FAST_STATUS_POLLING_INTERVAL: <%= Constants::FAST_STATUS_POLLING_INTERVAL %>,
SLOW_STATUS_POLLING_INTERVAL: <%= Constants::SLOW_STATUS_POLLING_INTERVAL %>,
ASSET_POLLING_INTERVAL: <%= Constants::ASSET_POLLING_INTERVAL %>,
ASSET_SYNC_URL: '<%= Constants::ASSET_SYNC_URL %>',
GLOBAL_SEARCH_PREVIEW_LIMIT: <%= Constants::GLOBAL_SEARCH_PREVIEW_LIMIT %>,
SEARCH_LIMIT: <%= Constants::SEARCH_LIMIT %>,
SCINOTE_EDIT_RESTRICTED_EXTENSIONS: <%= Constants::SCINOTE_EDIT_RESTRICTED_EXTENSIONS %>
};

View file

@ -182,7 +182,7 @@ var zebraPrint = (function() {
repository_id: int
}
*/
print: function(modalUrl, progressModal, printModal, printData) {
print: function(modalUrl, progressModal, printModal, printData, finishedCallback = null) {
var modal = $(progressModal);
$.ajax({
method: 'GET',
@ -224,7 +224,7 @@ var zebraPrint = (function() {
}
}).fail(() => {
HelperModule.flashAlertMsg(I18n.t('repository_row.modal_print_label.general_error'), 'danger');
});
}).always(() => { if (finishedCallback) finishedCallback(); });
}
};
}());

View file

@ -36,3 +36,9 @@ table.dataTable.table--resizable-columns {
}
}
}
div.dataTables_wrapper div.dataTables_processing {
// This overrides default library behaviour, where the 'Processing' element
// can go off-screen on large tables due to top: 50% positioning.
top: 150px !important;
}

View file

@ -0,0 +1,38 @@
# frozen_string_literal: true
class GlobalConstantsController < ApplicationController
before_action :load_global_constants
skip_before_action :authenticate_user!, only: :index
skip_before_action :verify_authenticity_token, only: :index
def index; end
private
def load_global_constants
@global_constants = {
NAME_TRUNCATION_LENGTH: Constants::NAME_TRUNCATION_LENGTH,
NAME_MAX_LENGTH: Constants::NAME_MAX_LENGTH,
NAME_MIN_LENGTH: Constants::NAME_MIN_LENGTH,
TEXT_MAX_LENGTH: Constants::TEXT_MAX_LENGTH,
TABLE_CARD_MIN_WIDTH: 340,
TABLE_CARD_GAP: 16,
FILENAME_TRUNCATION_LENGTH: Constants::FILENAME_TRUNCATION_LENGTH,
FILE_MAX_SIZE_MB: Rails.configuration.x.file_max_size_mb,
REPOSITORY_LIST_ITEMS_PER_COLUMN: Constants::REPOSITORY_LIST_ITEMS_PER_COLUMN,
REPOSITORY_CHECKLIST_ITEMS_PER_COLUMN: Constants::REPOSITORY_CHECKLIST_ITEMS_PER_COLUMN,
REPOSITORY_STOCK_UNIT_ITEMS_PER_COLUMN: Constants::REPOSITORY_STOCK_UNIT_ITEMS_PER_COLUMN,
HAS_UNSAVED_DATA_CLASS_NAME: 'has-unsaved-data',
DEFAULT_ELEMENTS_PER_PAGE: Constants::DEFAULT_ELEMENTS_PER_PAGE,
FILENAME_MAX_LENGTH: Constants::FILENAME_MAX_LENGTH,
FAST_STATUS_POLLING_INTERVAL: Constants::FAST_STATUS_POLLING_INTERVAL,
SLOW_STATUS_POLLING_INTERVAL: Constants::SLOW_STATUS_POLLING_INTERVAL,
ASSET_POLLING_INTERVAL: Constants::ASSET_POLLING_INTERVAL,
ASSET_SYNC_URL: Constants::ASSET_SYNC_URL,
GLOBAL_SEARCH_PREVIEW_LIMIT: Constants::GLOBAL_SEARCH_PREVIEW_LIMIT,
SEARCH_LIMIT: Constants::SEARCH_LIMIT,
SCINOTE_EDIT_RESTRICTED_EXTENSIONS: Constants::SCINOTE_EDIT_RESTRICTED_EXTENSIONS,
SCINOTE_EDIT_LATEST_JSON_URL: Constants::SCINOTE_EDIT_LATEST_JSON_URL
}
end
end

View file

@ -9,15 +9,16 @@ class RepositoriesController < ApplicationController
include RepositoriesDatatableHelper
include MyModulesHelper
before_action :switch_team_with_param, only: %i(index)
before_action :load_repository, except: %i(index create create_modal sidebar archive restore actions_toolbar
export_modal export_repositories list)
export_repositories list)
before_action :load_repositories, only: %i(index list)
before_action :load_repositories_for_archiving, only: :archive
before_action :load_repositories_for_restoring, only: :restore
before_action :check_view_all_permissions, only: %i(index sidebar list)
before_action :check_view_permissions, except: %i(index create_modal create update destroy parse_sheet
import_records sidebar archive restore actions_toolbar
export_modal export_repositories list)
export_repositories list)
before_action :check_manage_permissions, only: %i(rename_modal update)
before_action :check_delete_permissions, only: %i(destroy destroy_modal)
before_action :check_archive_permissions, only: %i(archive restore)
@ -83,7 +84,6 @@ class RepositoriesController < ApplicationController
def show
respond_to do |format|
format.html do
current_team_switch(@repository.team) unless @repository.shared_with?(current_team)
@display_edit_button = can_create_repository_rows?(@repository)
@display_delete_button = can_delete_repository_rows?(@repository)
@display_duplicate_button = can_create_repository_rows?(@repository)
@ -470,7 +470,7 @@ class RepositoriesController < ApplicationController
def load_repository
repository_id = params[:id] || params[:repository_id]
@repository = Repository.viewable_by_user(current_user).find_by(id: repository_id)
@repository = Repository.viewable_by_user(current_user, current_user.teams).find_by(id: repository_id)
render_404 unless @repository
end
@ -515,6 +515,7 @@ class RepositoriesController < ApplicationController
end
def check_view_permissions
current_team_switch(@repository.team) unless @repository.shared_with?(current_team)
render_403 unless can_read_repository?(@repository)
end

View file

@ -3,6 +3,7 @@
class ResultsController < ApplicationController
include Breadcrumbs
include TeamsHelper
before_action :load_my_module
before_action :load_vars, only: %i(destroy elements assets upload_attachment archive restore destroy
update_view_state update_asset_view_mode update duplicate)

View file

@ -114,7 +114,7 @@ class SearchController < ApplicationController
return
when 'repository_rows'
@model = RepositoryRow
search_by_name(RepositoryRow)
search_by_name
render json: @records,
each_serializer: GlobalSearch::RepositoryRowSerializer,

View file

@ -11,7 +11,7 @@
<div v-if="!loading && actions.length === 0" class="sn-action-toolbar__message">
{{ i18n.t('action_toolbar.no_actions') }}
</div>
<div v-for="action in actions" :key="action.name" class="sn-action-toolbar__action shrink-0">
<div v-for="action in actions" :key="action.name" class="sn-action-toolbar__action shrink-0" :class="{ 'disable-click': disabledActions[action.name] }">
<div v-if="action.type === 'group' && Array.isArray(action.actions) && action.actions.length > 1" class="export-actions-dropdown sci-dropdown dropup">
<button class="btn btn-primary dropdown-toggle single-object-action rounded" type="button" id="exportDropdown" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true" data-e2e="e2e-DD-actionToolbar-export">
<i class="sn-icon sn-icon-export"></i>
@ -75,6 +75,7 @@
<script>
import { debounce } from '../shared/debounce.js';
import axios from '../../packs/custom_axios.js';
export default {
name: 'ActionToolbar',
@ -95,7 +96,8 @@ export default {
bottomOffset: 0,
leftOffset: 0,
buttonOverflow: false,
submitting: false
submitting: false,
disabledActions: {}
};
},
created() {
@ -105,8 +107,8 @@ export default {
this.debouncedFetchActions = debounce((params) => {
this.params = params;
$.get(`${this.actionsUrl}?${new URLSearchParams(this.params).toString()}`, (data) => {
this.actions = data.actions;
axios.post(this.actionsUrl, this.params).then((response) => {
this.actions = response.data.actions;
this.loading = false;
this.setButtonOverflow();
if (this.actionsLoadedCallback) this.$nextTick(this.actionsLoadedCallback);
@ -168,6 +170,12 @@ export default {
this.actionsLoadedCallback = func;
},
doAction(action, event) {
this.disabledActions[action.name] = true;
setTimeout(() => {
delete this.disabledActions[action.name];
}, 1000); // enable action after one second, to prevent multi-clicks
switch (action.type) {
case 'legacy':
// do nothing, this is handled by legacy code based on the button class

View file

@ -367,6 +367,7 @@ export default {
deleteSteps() {
$.post(this.urls.delete_steps_url, () => {
this.steps = [];
this.refreshProtocolDropdownOptions();
}).fail(() => {
HelperModule.flashAlertMsg(this.i18n.t('errors.general'), 'danger');
});

View file

@ -238,6 +238,9 @@ export default {
label_template_id: this.selectedTemplate.id,
row_ids: this.row_ids,
repository_id: this.repository_id
},
() => {
this.submitting = false;
}
);
} else {

View file

@ -1,5 +1,5 @@
<template>
<div class="p-4 w-full rounded bg-sn-light-grey min-h-[68px]" :class="{ 'disable-click': submitting }" data-e2e="e2e-CO-actionToolbar">
<div class="p-4 w-full rounded bg-sn-light-grey min-h-[68px]" data-e2e="e2e-CO-actionToolbar">
<div class="flex gap-4 items-center h-full">
<div v-if="loading && !actions.length" class="sn-action-toolbar__action">
<a class="rounded flex items-center py-1.5 px-2.5 bg-transparent text-transparent no-underline"></a>
@ -7,7 +7,7 @@
<div v-if="!loading && actions.length === 0" class="text-sn-grey-grey">
{{ i18n.t('action_toolbar.no_actions') }}
</div>
<div v-for="action in actions" :key="action.name" class="sn-action-toolbar__action shrink-0">
<div v-for="action in actions" :key="action.name" class="sn-action-toolbar__action shrink-0" :class="{ 'disable-click': disabledActions[action.name] }">
<a :class="`rounded flex gap-2 items-center py-1.5 px-1.5 xl:px-2.5 hover:text-sn-white hover:bg-sn-blue
bg-sn-white color-sn-blue hover:no-underline focus:no-underline ${action.button_class}`"
:href="(['link', 'remote-modal']).includes(action.type) ? action.path : '#'"
@ -32,7 +32,7 @@ export default {
name: 'ActionToolbar',
props: {
actionsUrl: { type: String, required: true },
actionsMethod: { type: String, default: 'get' },
actionsMethod: { type: String, default: 'post' },
params: { type: Object },
},
data() {
@ -42,7 +42,7 @@ export default {
reloadCallback: null,
loaded: false,
loading: true,
submitting: false
disabledActions: {}
};
},
watch: {
@ -70,10 +70,15 @@ export default {
});
},
doAction(action, event) {
this.disabledActions[action.name] = true;
setTimeout(() => {
delete this.disabledActions[action.name];
}, 1000); // enable action after one second, to prevent multi-clicks
switch (action.type) {
case 'emit':
event.preventDefault();
this.submitting = true;
this.$emit('toolbar:action', action);
break;
case 'modal':

View file

@ -50,7 +50,7 @@ export default {
methods: {
loadActions() {
if (this.actionsMenu.length > 0) return;
axios.get(this.params.data.urls.actions)
axios.post(this.params.data.urls.actions)
.then((response) => {
this.actionsMenu = response.data.actions;
});

View file

@ -114,7 +114,7 @@ export default {
},
methods: {
fetchData() {
$.get('https://extras.scinote.net/scinote-edit/latest.json', (result) => {
$.get(GLOBAL_CONSTANTS.SCINOTE_EDIT_LATEST_JSON_URL, (result) => {
this.responseData = result;
});
}

View file

@ -3,6 +3,8 @@
require 'caxlsx'
class FormResponsesZipExportJob < ZipExportJob
include StringUtility
private
# Override
@ -11,7 +13,7 @@ class FormResponsesZipExportJob < ZipExportJob
exported_data = to_xlsx(form)
File.binwrite("#{dir}/#{form.name}.xlsx", exported_data)
File.binwrite("#{dir}/#{to_filesystem_name(form.name)}.xlsx", exported_data)
end
def failed_notification_title

View file

@ -1,6 +1,8 @@
# frozen_string_literal: true
class RepositoryItemDateReminderJob < ApplicationJob
BUFFER_DAYS = 2
queue_as :default
def perform
@ -11,33 +13,37 @@ class RepositoryItemDateReminderJob < ApplicationJob
private
def process_repository_values(model, comparison_value)
def repository_values_due(model, comparison_value)
model
.joins(repository_cell: [:repository_row, { repository_column: :repository }])
.where(
notification_sent: false,
repositories: { type: 'Repository', archived: false },
repository_rows: { archived: false }
).where('repository_date_time_values.updated_at >= ?', 2.days.ago)
.where( # date(time) values that are within the reminder range
"data <= " \
"(?::timestamp + CAST(((repository_columns.metadata->>'reminder_unit')::int * " \
).where( # date(time) values that are within the reminder range including buffer
"(data > (:comparison_value::timestamp - (INTERVAL ':buffer_days DAY'))) AND data <= " \
"(:comparison_value::timestamp + CAST(((repository_columns.metadata->>'reminder_unit')::int * " \
"(repository_columns.metadata->>'reminder_value')::int) || ' seconds' AS Interval))",
comparison_value
).find_each do |value|
repository_row = RepositoryRow.find(value.repository_cell.repository_row_id)
repository_column = RepositoryColumn.find(value.repository_cell.repository_column_id)
buffer_days: BUFFER_DAYS,
comparison_value: comparison_value
)
end
RepositoryItemDateNotification
.send_notifications({
"#{value.class.name.underscore}_id": value.id,
repository_row_id: repository_row.id,
repository_row_name: repository_row.name,
repository_column_id: repository_column.id,
repository_column_name: repository_column.name,
reminder_unit: repository_column.metadata['reminder_unit'],
reminder_value: repository_column.metadata['reminder_value']
})
end
def process_repository_values(model, comparison_value)
repository_values_due(model, comparison_value).find_each do |value|
repository_row = RepositoryRow.find(value.repository_cell.repository_row_id)
repository_column = RepositoryColumn.find(value.repository_cell.repository_column_id)
RepositoryItemDateNotification
.send_notifications({
"#{value.class.name.underscore}_id": value.id,
repository_row_id: repository_row.id,
repository_row_name: repository_row.name,
repository_column_id: repository_column.id,
repository_column_name: repository_column.name,
reminder_unit: repository_column.metadata['reminder_unit'],
reminder_value: repository_column.metadata['reminder_value']
})
end
end
end

View file

@ -299,13 +299,13 @@ class Asset < ApplicationRecord
# Extract only the licenced user flag parameter
is_licenced_user = ENV['WOPI_BUSINESS_USERS'] == 'true' && action_url.include?('IsLicensedUser=BUSINESS_USER')
action_url = action_url.split('?').first + "?IsLicencedUser=#{is_licenced_user ? 1 : 0}&"
action_url = action_url.split(/<.*>/).first + "IsLicencedUser=#{is_licenced_user ? 1 : 0}"
rest_url = Rails.application.routes.url_helpers.wopi_rest_endpoint_url(
host: ENV['WOPI_ENDPOINT_URL'],
id: id
)
action_url += "WOPISrc=#{rest_url}"
action_url += "&WOPISrc=#{rest_url}"
if with_tokens
token = user.get_wopi_token
action_url + "&access_token=#{token.token}"\

View file

@ -137,11 +137,7 @@ class RepositoryRow < ApplicationRecord
teams = options[:teams] || current_team || user.teams.select(:id)
searchable_row_fields = [RepositoryRow::PREFIXED_ID_SQL, 'repository_rows.name', 'users.full_name']
readable_rows =
distinct
.joins(:repository, :created_by)
.where(repositories: { id: Repository.with_granted_permissions(user, RepositoryPermissions::READ).select(:id),
team_id: teams })
readable_rows = distinct.joins(:repository, :created_by).viewable_by_user(user, teams)
readable_rows = readable_rows.active unless include_archived
repository_rows = readable_rows.where_attributes_like_boolean(searchable_row_fields, query, options)

View file

@ -273,8 +273,8 @@ class TeamImporter
updated = true
end
end
text.scan(/\[@[\w+-@?! ]+~\w+\]/).each do |user_match|
orig_id_encoded = user_match.match(/\[@[\w+-@?! ]+~(\w+)\]/)[1]
text.scan(SmartAnnotations::TagToText::USER_REGEX).each do |user_match|
orig_id_encoded = user_match[1]
orig_id = orig_id_encoded.base62_decode
next unless @user_mappings[orig_id]

View file

@ -1,6 +1,7 @@
<html>
<head>
<%= csp_meta_tag %>
<%= csrf_meta_tags %>
<%= javascript_include_tag 'i18n_bundle' %>
<%= javascript_include_tag 'jquery_bundle' %>
<%= stylesheet_link_tag 'sn_icon_font' %>

View file

@ -0,0 +1 @@
const GLOBAL_CONSTANTS = <%= @global_constants.to_json.html_safe %>;

View file

@ -5,7 +5,6 @@
<meta data-hook="head-js">
<title><%=t "head.title", title: (yield :head_title) %></title>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<meta name="max-file-size" content="<%= Rails.configuration.x.file_max_size_mb %>">
<meta name="tiny-mce-assets-url" content="<%= tiny_mce_assets_path %>">
<% if user_signed_in? %>
<meta name="expiration-url" content="<%= users_expire_in_path %>">
@ -18,6 +17,8 @@
<% if ::NewRelic::Agent.instance.started? %>
<%= ::NewRelic::Agent.browser_timing_header(controller.request.content_security_policy_nonce) %>
<% end %>
<script src="<%= global_constants_path(format: :js) %>"></script>
<%= javascript_include_tag 'jquery_bundle' %>
<%= javascript_include_tag 'application_pack' %>
@ -79,7 +80,6 @@
data-datetime-picker-format-vue="<%= datetime_picker_format_date_only_vue %>"
<% end %>
>
<span style="display: none;" data-hook="body-js"></span>
<span style="display: none;" data-hook="application-body-html"></span>
@ -140,6 +140,5 @@
<%= javascript_include_tag 'prism' %>
<%= javascript_include_tag "vue_components_repository_item_sidebar" %>
</body>
</html>

View file

@ -5,6 +5,8 @@
<meta data-hook="head-js">
<title><%=t "head.title", title: (yield :head_title) %></title>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<script src="<%= global_constants_path(format: :js) %>"></script>
<%= stylesheet_link_tag "tailwind", "data-turbo-track": "reload" %>
<%= stylesheet_link_tag 'bootstrap_pack', media: 'all' %>
<%= stylesheet_link_tag 'application', media: 'all' %>

View file

@ -10,6 +10,8 @@
<style media="all">
html, body { height: 100%; min-height: 100%; }
</style>
<script src="<%= global_constants_path(format: :js) %>"></script>
<%= stylesheet_link_tag "tailwind", "data-turbo-track": "reload" %>
<%= stylesheet_link_tag 'bootstrap_pack', media: 'all' %>
<%= stylesheet_link_tag 'application_pack_styles', media: 'all' %>

View file

@ -458,6 +458,8 @@ class Constants
JSE LNK MSC MSI MSP MST PAF PIF PS1 REG RGS SCR SCT SHB SHS U3P VB VBE VBS VBSCRIPT WS WSF WSH
).freeze
SCINOTE_EDIT_LATEST_JSON_URL = ENV['SCINOTE_EDIT_LATEST_JSON_URL'].freeze
# quick search
QUICK_SEARCH_LIMIT = 5
QUICK_SEARCH_SEARCHABLE_OBJECTS = %w(project experiment my_module protocol repository_row

View file

@ -4196,8 +4196,8 @@ en:
body: "This file type appears to be executable and can potentially harm your computer. It cannot be opened with SciNote Edit. To open the file please download it manually."
action: "I understand"
update_version_modal:
title: "Update required"
body_text_html: "The current version of the SciNote Edit application is no longer supported. To ensure a seamless and secure user experience, we recommend updating to the latest version."
title: "Incompatible SciNote Edit version"
body_text_html: "Your current version of the SciNote Edit application is no longer supported. To ensure a seamless and secure experience, download the appropriate version by clicking the Download button below — it will automatically provide the correct version for you."
edit_launching_application_modal:
title: "Launching application"
description: "%{file_name} will now open in %{application}. Saved changes in %{application} will automatically be synced in SciNote."

View file

@ -7,6 +7,8 @@ Rails.application.routes.draw do
get 'api/health', to: 'api/api#health', as: 'api_health'
get 'api/status', to: 'api/api#status', as: 'api_status'
get '/global_constants', to: 'global_constants#index', as: 'global_constants'
post 'access_tokens/revoke', to: 'doorkeeper/access_tokens#revoke'
# Addons
@ -64,7 +66,7 @@ Rails.application.routes.draw do
get :template_tags
get :zpl_preview
post :sync_fluics_templates
get :actions_toolbar
post :actions_toolbar
end
end
@ -208,7 +210,7 @@ Rails.application.routes.draw do
defaults: { format: 'json' }
get 'create_modal', to: 'repositories#create_modal',
defaults: { format: 'json' }
get 'actions_toolbar'
post 'actions_toolbar'
get :list
get :rows_list
end
@ -285,7 +287,7 @@ Rails.application.routes.draw do
end
collection do
get :project_contents
get 'actions_toolbar'
post 'actions_toolbar'
end
end
get 'reports/datatable', to: 'reports#datatable'
@ -393,7 +395,7 @@ Rails.application.routes.draw do
get 'users_filter'
post 'archive_group'
post 'restore_group'
get 'actions_toolbar'
post 'actions_toolbar'
get :user_roles
end
end
@ -418,7 +420,7 @@ Rails.application.routes.draw do
get 'inventory_assigning_experiment_filter'
get 'clone_modal', action: :clone_modal
get 'move_modal', action: :move_modal
get 'actions_toolbar'
post 'actions_toolbar'
get 'move_modal' # return modal with move options
post 'move' # move experiment
end
@ -464,7 +466,7 @@ Rails.application.routes.draw do
post 'save_table_state', on: :collection, defaults: { format: 'json' }
collection do
get 'actions_toolbar'
post 'actions_toolbar'
get 'inventory_assigning_my_module_filter'
end
@ -503,8 +505,8 @@ Rails.application.routes.draw do
get :full_view_table
post :index_dt, defaults: { format: 'json' }
post :export_repository
get :assign_repository_records_modal, as: :assign_modal
get :update_repository_records_modal, as: :update_modal
post :assign_repository_records_modal, as: :assign_modal
post :update_repository_records_modal, as: :update_modal
get :consume_modal
post :update_consumption
end
@ -709,7 +711,7 @@ Rails.application.routes.draw do
post 'protocolsio_import_save', to: 'protocols#protocolsio_import_save'
get 'export', to: 'protocols#export'
get 'protocolsio', to: 'protocols#protocolsio_index'
get 'actions_toolbar', to: 'protocols#actions_toolbar'
post 'actions_toolbar', to: 'protocols#actions_toolbar'
get 'user_roles', to: 'protocols#user_roles'
end
end
@ -775,7 +777,7 @@ Rails.application.routes.draw do
end
collection do
get :actions_toolbar
post :actions_toolbar
end
resources :repository_row_connections, only: %i(create destroy) do
@ -845,7 +847,7 @@ Rails.application.routes.draw do
resources :storage_locations, only: %i(index create destroy update show) do
collection do
get :actions_toolbar
post :actions_toolbar
get :tree
end
member do
@ -875,7 +877,7 @@ Rails.application.routes.draw do
end
collection do
get :actions_toolbar
post :actions_toolbar
post :archive
post :restore
get :user_roles

View file

@ -19,6 +19,10 @@ describe ReportsController, type: :controller do
describe 'POST create' do
context 'in JSON format' do
before do
allow(Reports::PdfJob).to receive(:perform_later)
end
let(:action) { post :create, params: params, format: :json }
let(:params) do
{ project_id: project.id,
@ -42,11 +46,14 @@ describe ReportsController, type: :controller do
.to(change { Activity.count })
end
end
# Temporary disabled due to webpack problems
end if false
end
describe 'PUT update' do
context 'in JSON format' do
before do
allow(Reports::PdfJob).to receive(:perform_later)
end
let(:action) { put :update, params: params, format: :json }
let(:params) do
{ project_id: project.id,
@ -69,8 +76,7 @@ describe ReportsController, type: :controller do
.to(change { Activity.count })
end
end
# Temporary disabled due to webpack problems
end if false
end
describe 'DELETE destroy' do
let(:action) { delete :destroy, params: params }

View file

@ -45,11 +45,11 @@ describe RepositoryRowsController, type: :controller do
end
it 'successful response' do
allow_any_instance_of(ActionView::Helpers::AssetUrlHelper).to receive(:asset_path)
get :show, format: :json, params: { repository_id: repository.id, id: repository_row.id }
expect(response).to have_http_status(:success)
end
# Temporary disabled due to webpack problems
end if false
end
context '#index' do
before do

View file

@ -9,6 +9,18 @@ describe TeamsController, type: :controller do
let(:team) { create :team, created_by: user }
describe 'POST export_projects' do
before do
view_response = '<div class="content-pane" id="report-new"><div class="report-container"><div id="report-content"></div></div></div>'
proxy_manager = Warden::Manager.new({})
proxy = Warden::Proxy.new({}, proxy_manager)
renderer_double = double('Renderer', render: view_response.dup)
allow(Warden::Manager).to receive(:new).and_return(proxy_manager)
allow(Warden::Proxy).to receive(:new).with({}, proxy_manager).and_return(proxy)
allow(ApplicationController.renderer).to receive(:new).with({ warden: proxy }).and_return(renderer_double)
allow(ApplicationController).to receive(:render)
end
let!(:first_project) { create :project, team: team, created_by: user }
let!(:second_project) { create :project, team: team, created_by: user }
let(:params) do
@ -31,6 +43,5 @@ describe TeamsController, type: :controller do
expect { action }
.to(change { Activity.count })
end
# Temporary disabled due to webpack problems
end if false
end
end

View file

@ -0,0 +1,87 @@
# frozen_string_literal: true
require 'rails_helper'
describe RepositoryItemDateReminderJob, type: :job do
around do |example|
Timecop.freeze(Time.utc(2025, 1, 15, 0, 0, 0)) do
example.run
end
end
let(:user) { create :user }
let(:team) { create :team, created_by: user }
let!(:owner_role) { UserRole.find_by(name: I18n.t('user_roles.predefined.owner')) }
let!(:team_assignment) { create_user_assignment(team, owner_role, user) }
let(:repository) { create :repository, team: team, created_by: user }
let!(:date_column_with_reminder) do
create :repository_column, repository: repository,
created_by: user,
name: 'Custom items',
data_type: 'RepositoryDateValue',
metadata: {"reminder_unit"=>"86400", "reminder_value"=>"5", "reminder_message"=>""}
end
let!(:repository_date_value_due) do
row = create :repository_row, name: "row 1",
repository: repository,
created_by: user,
last_modified_by: user
create(
:repository_date_value,
data: Date.parse('20-1-2025'),
repository_cell_attributes: { repository_row: row, repository_column: date_column_with_reminder }
)
end
let!(:repository_date_value_due_outside_buffer) do
row = create :repository_row, name: "row 1",
repository: repository,
created_by: user,
last_modified_by: user
create(
:repository_date_value,
data: Date.parse('15-1-2025') - (RepositoryItemDateReminderJob::BUFFER_DAYS + 1).days,
repository_cell_attributes: { repository_row: row, repository_column: date_column_with_reminder }
)
end
let!(:repository_date_value_due_inside_buffer) do
row = create :repository_row, name: "row 1",
repository: repository,
created_by: user,
last_modified_by: user
create(
:repository_date_value,
data: Date.parse('15-1-2025') - (RepositoryItemDateReminderJob::BUFFER_DAYS - 1).days,
repository_cell_attributes: { repository_row: row, repository_column: date_column_with_reminder }
)
end
let!(:repository_date_value_not_due) do
row = create :repository_row, name: "row 1",
repository: repository,
created_by: user,
last_modified_by: user
create(
:repository_date_value,
data: Date.parse('10-10-2025'),
repository_cell_attributes: { repository_row: row, repository_column: date_column_with_reminder }
)
end
describe '#repository_values_due' do
it "returns repository values that are due with a #{RepositoryItemDateReminderJob::BUFFER_DAYS} day buffer" do
values = described_class.new.send(:repository_values_due, RepositoryDateValue, Date.current)
expect(values).to include(repository_date_value_due)
expect(values).to_not include(repository_date_value_not_due)
expect(values).to include(repository_date_value_due_inside_buffer)
expect(values).to_not include(repository_date_value_due_outside_buffer)
end
end
end

View file

@ -228,11 +228,8 @@
<glyph unicode="&#xe9c7;" glyph-name="text-fields" data-tags="text-fields" d="M336.406 144.414v554.668h-213.333v52.523h479.188v-52.523h-213.332v-554.668h-52.523zM720.405 144.414v341.333h-128v52.524h308.523v-52.524h-128v-341.333h-52.523z" />
<glyph unicode="&#xe9c8;" glyph-name="merge-cells" data-tags="merge-cells" d="M149.333 85.333v213.333h42.667v-170.667h170.667v-42.667h-213.333zM661.333 85.333v42.667h170.667v170.667h42.667v-213.333h-213.333zM306.133 315.157l-30.443 29.376 82.133 82.133h-251.157v42.667h251.157l-82.133 82.133 30.443 29.376 132.843-132.843-132.843-132.843zM717.867 315.157l-132.843 132.843 132.843 132.843 30.443-29.376-82.133-82.133h251.157v-42.667h-251.157l82.133-82.133-30.443-29.376zM149.333 597.333v213.333h213.333v-42.667h-170.667v-170.667h-42.667zM832 597.333v170.667h-170.667v42.667h213.333v-213.333h-42.667z" />
<glyph unicode="&#xe9c9;" glyph-name="text-Italic" data-tags="text-Italic" d="M246.976 181.333v47.595h155.072l148.514 438.144h-155.074v47.595h354.464v-47.595h-150.153l-148.514-438.144h150.157v-47.595h-354.466z" />
<<<<<<< HEAD
=======
<glyph unicode="&#xe9ca;" glyph-name="forms" data-tags="forms" d="M320 256h256v42.667h-256v-42.667zM320 426.667h384v42.667h-384v-42.667zM320 597.333h384v42.667h-384v-42.667zM239.595 106.667c-19.641 0-36.039 6.579-49.195 19.733s-19.733 29.555-19.733 49.195v544.811c0 19.641 6.578 36.039 19.733 49.195s29.554 19.733 49.195 19.733h544.811c19.639 0 36.041-6.578 49.195-19.733s19.733-29.554 19.733-49.195v-544.811c0-19.639-6.579-36.041-19.733-49.195s-29.555-19.733-49.195-19.733h-544.811zM239.595 149.333h544.811c6.571 0 12.591 2.735 18.057 8.205 5.47 5.466 8.205 11.486 8.205 18.057v544.811c0 6.571-2.735 12.59-8.205 18.059-5.466 5.469-11.486 8.203-18.057 8.203h-544.811c-6.571 0-12.59-2.734-18.059-8.203s-8.203-11.488-8.203-18.059v-544.811c0-6.571 2.734-12.591 8.203-18.057 5.469-5.47 11.488-8.205 18.059-8.205z" />
<glyph unicode="&#xe9cb;" glyph-name="value" data-tags="value" d="M512.141 64c-53.103 0-103.024 10.078-149.773 30.229-46.741 20.151-87.403 47.501-121.984 82.048s-61.955 75.17-82.123 121.877c-20.174 46.699-30.261 96.602-30.261 149.705s10.077 103.024 30.229 149.773c20.153 46.741 47.502 87.403 82.048 121.984s75.171 61.955 121.877 82.123c46.699 20.174 96.602 30.261 149.705 30.261s103.023-10.077 149.773-30.229c46.741-20.153 87.403-47.502 121.984-82.048s61.956-75.171 82.125-121.877c20.173-46.699 30.259-96.602 30.259-149.705s-10.078-103.023-30.229-149.773c-20.151-46.741-47.501-87.403-82.048-121.984s-75.17-61.956-121.877-82.125c-46.699-20.173-96.602-30.259-149.705-30.259zM512 106.667c95.287 0 176 33.067 242.133 99.2s99.2 146.846 99.2 242.133c0 95.289-33.067 176-99.2 242.133s-146.846 99.2-242.133 99.2c-95.289 0-176-33.067-242.133-99.2s-99.2-146.845-99.2-242.133c0-95.287 33.067-176 99.2-242.133s146.845-99.2 242.133-99.2zM512 256h42.667v384h-128v-42.667h85.333v-341.333z" />
<glyph unicode="&#xe9cc;" glyph-name="choice-multiple" data-tags="choice-multiple" d="M170.667 746.667c0 23.564 19.103 42.667 42.667 42.667h597.333c23.565 0 42.667-19.103 42.667-42.667v-597.333c0-23.565-19.102-42.667-42.667-42.667h-597.333c-23.564 0-42.667 19.102-42.667 42.667v597.333zM810.667 746.667h-597.333v-597.333h597.333v597.333z" />
<glyph unicode="&#xe9cd;" glyph-name="choice-single" data-tags="choice-single" d="M853.333 448c0-188.514-152.819-341.333-341.333-341.333s-341.333 152.819-341.333 341.333c0 188.513 152.82 341.333 341.333 341.333s341.333-152.82 341.333-341.333zM512 149.333c164.949 0 298.667 133.717 298.667 298.667s-133.717 298.667-298.667 298.667c-164.949 0-298.667-133.718-298.667-298.667s133.718-298.667 298.667-298.667z" />
>>>>>>> develop
</font></defs></svg>

Before

Width:  |  Height:  |  Size: 149 KiB

After

Width:  |  Height:  |  Size: 149 KiB

View file

@ -5681,11 +5681,32 @@ tiny-emitter@^2.1.0:
resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423"
integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==
tiny-invariant@^1.0.2, tiny-invariant@^1.0.6:
version "1.3.1"
resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.1.tgz#8560808c916ef02ecfd55e66090df23a4b7aa642"
integrity sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==
tiny-warning@^1.0.0:
version "1.0.3"
resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754"
integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==
tinycolor2@^1.4.1:
version "1.6.0"
resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.6.0.tgz#f98007460169b0263b97072c5ae92484ce02d09e"
integrity sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==
tinymce@^6.8.5:
version "6.8.5"
resolved "https://registry.yarnpkg.com/tinymce/-/tinymce-6.8.5.tgz#aa9a711c4e0b59d506dd281bade857d35a7b3c59"
integrity sha512-qAL/FxL7cwZHj4BfaF818zeJJizK9jU5IQzTcSLL4Rj5MaJdiVblEj7aDr80VCV1w9h4Lak9hlnALhq/kVtN1g==
tippy.js@^6.3.7:
version "6.3.7"
resolved "https://registry.yarnpkg.com/tippy.js/-/tippy.js-6.3.7.tgz#8ccfb651d642010ed9a32ff29b0e9e19c5b8c61c"
integrity sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==
dependencies:
"@popperjs/core" "^2.9.0"
to-fast-properties@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e"