mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-12-09 21:56:32 +08:00
Merge branch 'develop'
This commit is contained in:
commit
729cb9b22c
79 changed files with 1117 additions and 331 deletions
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
|||
1.34.0.2
|
||||
1.35.0
|
||||
|
|
|
|||
|
|
@ -147,10 +147,11 @@
|
|||
// initialize my_module tab remote loading
|
||||
$('#experimentTable, .my-modules-protocols-index, #experiment-canvas')
|
||||
.on('click', '.edit-tags-link', function() {
|
||||
if($('#tagsModalComponent').length) {
|
||||
$('#tagsModalComponent').data('tagsModal').open()
|
||||
if (window.tagsModal) {
|
||||
const url = $(this).closest('.module-large').data('module-url') || $(this).attr('href');
|
||||
window.tagsModal.open(url);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
bindEditTagsAjax();
|
||||
|
|
|
|||
|
|
@ -976,7 +976,7 @@ function bindEditTagsAjax(elements) {
|
|||
$(elements).find("a.edit-tags-link")
|
||||
.on('click', function(){
|
||||
$(this).addClass('updated-module-tags');
|
||||
var modal = $(this).closest(".panel-default").find('.tags-modal-component').data('tagsModal').open();
|
||||
var modal = $('#tagsModalComponent');
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -201,23 +201,27 @@ var zebraPrint = (function() {
|
|||
});
|
||||
}
|
||||
|
||||
updateProgressModalData(progressModal, printData.printer_name, PRINTER_STATUS_SEARCH, PRINTER_STATUS_SEARCH);
|
||||
device = findDevice(printData.printer_name);
|
||||
if (dataZebra?.responseJSON?.labels?.length && printData.number_of_copies > 0) {
|
||||
updateProgressModalData(progressModal, printData.printer_name, PRINTER_STATUS_SEARCH, PRINTER_STATUS_SEARCH);
|
||||
device = findDevice(printData.printer_name);
|
||||
|
||||
getPrinterStatus(device).then((device) => {
|
||||
if (device.status === I18n.t('label_printers.modal_printing_status.printer_status.ready')) {
|
||||
print(
|
||||
device,
|
||||
progressModal,
|
||||
printData.number_of_copies,
|
||||
printData.printer_name,
|
||||
dataZebra.responseJSON.labels,
|
||||
0,
|
||||
);
|
||||
} else {
|
||||
updateProgressModalData(progressModal, printData.printer_name, PRINTER_STATUS_ERROR, PRINTER_STATUS_ERROR);
|
||||
}
|
||||
});
|
||||
getPrinterStatus(device).then((device) => {
|
||||
if (device.status === I18n.t('label_printers.modal_printing_status.printer_status.ready')) {
|
||||
print(
|
||||
device,
|
||||
progressModal,
|
||||
printData.number_of_copies,
|
||||
printData.printer_name,
|
||||
dataZebra.responseJSON.labels,
|
||||
0
|
||||
);
|
||||
} else {
|
||||
updateProgressModalData(progressModal, printData.printer_name, PRINTER_STATUS_ERROR, PRINTER_STATUS_ERROR);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
updateProgressModalData(progressModal, printData.printer_name, PRINTER_STATUS_ERROR, PRINTER_STATUS_ERROR);
|
||||
}
|
||||
}).fail(() => {
|
||||
HelperModule.flashAlertMsg(I18n.t('repository_row.modal_print_label.general_error'), 'danger');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -106,6 +106,14 @@ module Api
|
|||
)
|
||||
end
|
||||
|
||||
rescue_from Api::V1::ApiKeyError do |e|
|
||||
render_error(
|
||||
e.message,
|
||||
I18n.t('api.core.invalid_api_key_detail'),
|
||||
:unauthorized
|
||||
)
|
||||
end
|
||||
|
||||
before_action :check_include_param, only: %i(index show)
|
||||
|
||||
def index
|
||||
|
|
|
|||
|
|
@ -141,7 +141,7 @@ class AssetSyncController < ApplicationController
|
|||
project = assoc.protocol.in_module? ? assoc.my_module.project : nil
|
||||
when Result
|
||||
type_of = :result_file_added
|
||||
message_items = { result: assoc }
|
||||
message_items = { result: assoc.id }
|
||||
project = assoc.my_module.project
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@ class AssetsController < ApplicationController
|
|||
helper_method :wopi_file_edit_button_status
|
||||
|
||||
before_action :load_vars, except: :create_wopi_file
|
||||
before_action :check_read_permission, except: %i(edit destroy create_wopi_file toggle_view_mode)
|
||||
before_action :check_edit_permission, only: %i(edit destroy toggle_view_mode)
|
||||
before_action :check_read_permission, except: %i(edit destroy duplicate create_wopi_file toggle_view_mode)
|
||||
before_action :check_manage_permission, only: %i(edit destroy duplicate rename toggle_view_mode)
|
||||
|
||||
def file_preview
|
||||
render json: { html: render_to_string(
|
||||
|
|
@ -311,6 +311,86 @@ class AssetsController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def rename
|
||||
new_name = params.require(:asset).permit(:name)[:name]
|
||||
|
||||
if new_name.empty?
|
||||
render json: { error: I18n.t('assets.rename_modal.min_length_error') }, status: :unprocessable_entity
|
||||
return
|
||||
elsif new_name.length > Constants::NAME_MAX_LENGTH
|
||||
render json: { error: I18n.t('assets.rename_modal.max_length_error') }, status: :unprocessable_entity
|
||||
return
|
||||
end
|
||||
|
||||
ActiveRecord::Base.transaction do
|
||||
old_name = @asset.file_name
|
||||
@asset.last_modified_by = current_user
|
||||
@asset.rename_file(new_name)
|
||||
@asset.save!
|
||||
|
||||
case @asset.parent
|
||||
when Step
|
||||
message_items = { old_name:, new_name:, user: current_user.id }
|
||||
message_items[:my_module] = @assoc.protocol.my_module.id if @assoc.protocol.in_module?
|
||||
@asset.parent.touch
|
||||
log_step_activity(
|
||||
"#{@assoc.protocol.in_module? ? 'task' : 'protocol'}_step_asset_renamed",
|
||||
@assoc,
|
||||
@assoc.my_module&.project,
|
||||
message_items
|
||||
)
|
||||
when Result
|
||||
log_result_activity(
|
||||
:result_asset_renamed,
|
||||
@assoc,
|
||||
old_name:,
|
||||
new_name:,
|
||||
user: current_user.id,
|
||||
my_module: @assoc.my_module.id
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
render json: @asset, serializer: AssetSerializer, user: current_user
|
||||
end
|
||||
|
||||
def duplicate
|
||||
ActiveRecord::Base.transaction do
|
||||
case @asset.parent
|
||||
when Step, Result
|
||||
new_asset = @asset.duplicate(
|
||||
new_name:
|
||||
"#{@asset.file.filename.base} (1).#{@asset.file.filename.extension}"
|
||||
)
|
||||
|
||||
@asset.parent.assets << new_asset
|
||||
end
|
||||
|
||||
case @asset.parent
|
||||
when Step
|
||||
message_items = { file: @asset.file_name }
|
||||
message_items[:my_module] = @assoc.protocol.my_module.id if @assoc.protocol.in_module?
|
||||
|
||||
log_step_activity(
|
||||
"#{@assoc.protocol.in_module? ? 'task' : 'protocol'}_step_file_duplicated",
|
||||
@assoc,
|
||||
@assoc.my_module&.project,
|
||||
message_items
|
||||
)
|
||||
when Result
|
||||
log_result_activity(
|
||||
:result_file_duplicated,
|
||||
@assoc,
|
||||
file: @asset.file_name,
|
||||
user: current_user.id,
|
||||
my_module: @assoc.my_module.id
|
||||
)
|
||||
end
|
||||
|
||||
render json: new_asset, serializer: AssetSerializer, user: current_user
|
||||
end
|
||||
end
|
||||
|
||||
def checksum
|
||||
render json: { checksum: @asset.file.blob.checksum }
|
||||
end
|
||||
|
|
@ -340,7 +420,7 @@ class AssetsController < ApplicationController
|
|||
render_403 and return unless can_read_asset?(@asset)
|
||||
end
|
||||
|
||||
def check_edit_permission
|
||||
def check_manage_permission
|
||||
render_403 and return unless can_manage_asset?(@asset)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,12 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Api
|
||||
module V1
|
||||
class ApiKeyError < StandardError
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module TokenAuthentication
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
|
|
@ -13,7 +20,23 @@ module TokenAuthentication
|
|||
raise JWT::InvalidPayload, I18n.t('api.core.no_azure_user_mapping') unless current_user
|
||||
end
|
||||
|
||||
def authenticate_with_api_key
|
||||
return unless Rails.configuration.x.core_api_key_enabled
|
||||
|
||||
@api_key = request.headers['Api-Key']
|
||||
return unless @api_key
|
||||
|
||||
@current_user = User.from_api_key(@api_key)
|
||||
|
||||
raise Api::V1::ApiKeyError, I18n.t('api.core.invalid_api_key') unless @current_user
|
||||
|
||||
@current_user
|
||||
end
|
||||
|
||||
def authenticate_request!
|
||||
# API key authentication successful
|
||||
return if authenticate_with_api_key
|
||||
|
||||
@token = request.headers['Authorization']&.sub('Bearer ', '')
|
||||
raise JWT::VerificationError, I18n.t('api.core.missing_token') unless @token
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ class MarvinJsAssetsController < ApplicationController
|
|||
before_action :load_create_vars, only: :create
|
||||
|
||||
before_action :check_read_permission
|
||||
before_action :check_edit_permission, only: %i(update create start_editing)
|
||||
before_action :check_manage_permission, only: %i(update create start_editing)
|
||||
|
||||
def create
|
||||
result = MarvinJsService.create_sketch(marvin_params, current_user, current_team)
|
||||
|
|
@ -87,7 +87,7 @@ class MarvinJsAssetsController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def check_edit_permission
|
||||
def check_manage_permission
|
||||
if @assoc.class == Step
|
||||
return render_403 unless can_manage_step?(@assoc)
|
||||
elsif @assoc.class == Result
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ class MyModulesController < ApplicationController
|
|||
}
|
||||
end
|
||||
format.json do
|
||||
render json: @my_module, serializer: Lists::MyModuleSerializer, user: current_user
|
||||
render json: @my_module, serializer: Lists::MyModuleSerializer, controller: self, user: current_user
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ class ResultsController < ApplicationController
|
|||
|
||||
def destroy
|
||||
name = @result.name
|
||||
if @result.destroy
|
||||
if @result.discard
|
||||
log_activity(:destroy_result, { destroyed_result: name })
|
||||
render json: {}, status: :ok
|
||||
else
|
||||
|
|
|
|||
|
|
@ -207,6 +207,24 @@ class Users::RegistrationsController < Devise::RegistrationsController
|
|||
render json: { qr_code: create_2fa_qr_code(current_user) }
|
||||
end
|
||||
|
||||
def regenerate_api_key
|
||||
current_user.regenerate_api_key!
|
||||
|
||||
redirect_to(edit_user_registration_path(anchor: 'api-key'),
|
||||
flash: {
|
||||
success: t('users.registrations.edit.api_key.generated')
|
||||
})
|
||||
end
|
||||
|
||||
def revoke_api_key
|
||||
current_user.revoke_api_key!
|
||||
|
||||
redirect_to(edit_user_registration_path(anchor: 'api-key'),
|
||||
flash: {
|
||||
success: t('users.registrations.edit.api_key.revoked')
|
||||
})
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# Called upon creating User (before .save). Permits parameters and extracts
|
||||
|
|
|
|||
|
|
@ -4,63 +4,69 @@ import { createApp } from 'vue/dist/vue.esm-bundler.js';
|
|||
import TagsModal from '../../../vue/my_modules/modals/tags.vue';
|
||||
import { mountWithTurbolinks } from '../helpers/turbolinks.js';
|
||||
|
||||
window.initTagsModalComponent = (id) => {
|
||||
const app = createApp({
|
||||
data() {
|
||||
return {
|
||||
tagsModalOpen: false
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
$(this.$refs.tagsModal).data('tagsModal', this);
|
||||
},
|
||||
methods: {
|
||||
open() {
|
||||
this.tagsModalOpen = true;
|
||||
},
|
||||
close() {
|
||||
this.tagsModalOpen = false;
|
||||
},
|
||||
syncTags(tags) {
|
||||
// My module page
|
||||
if ($('#module-tags-selector').length) {
|
||||
const assignedTags = tags.filter((i) => i.assigned).map((i) => (
|
||||
{
|
||||
value: i.id,
|
||||
label: i.attributes.name,
|
||||
params: {
|
||||
color: i.attributes.color
|
||||
}
|
||||
}
|
||||
));
|
||||
dropdownSelector.setData('#module-tags-selector', assignedTags);
|
||||
const app = createApp({
|
||||
data() {
|
||||
return {
|
||||
myModuleParams: null,
|
||||
myModuleUrl: null,
|
||||
tagsModalOpen: false,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
window.tagsModal = this;
|
||||
},
|
||||
beforeUnmount() {
|
||||
delete window.tagsModal;
|
||||
},
|
||||
methods: {
|
||||
open(myModuleUrl) {
|
||||
this.myModuleUrl = myModuleUrl;
|
||||
$.ajax({
|
||||
url: myModuleUrl,
|
||||
type: 'GET',
|
||||
dataType: 'json',
|
||||
success: (data) => {
|
||||
this.myModuleParams = { ...data.data.attributes, id: data.data.id };
|
||||
this.tagsModalOpen = true;
|
||||
}
|
||||
});
|
||||
},
|
||||
close() {
|
||||
this.myModuleParams = null;
|
||||
this.myModuleUrl = null;
|
||||
this.tagsModalOpen = false;
|
||||
},
|
||||
syncTags(tags) {
|
||||
// My module page
|
||||
if ($('#module-tags-selector').length) {
|
||||
const assignedTags = tags.filter((i) => i.assigned).map((i) => (
|
||||
{
|
||||
value: i.id,
|
||||
label: i.attributes.name,
|
||||
params: {
|
||||
color: i.attributes.color
|
||||
}
|
||||
}
|
||||
));
|
||||
dropdownSelector.setData('#module-tags-selector', assignedTags);
|
||||
}
|
||||
|
||||
// Canvas
|
||||
if ($('#canvas-container').length) {
|
||||
$.ajax({
|
||||
url: $('#canvas-container').attr('data-module-tags-url'),
|
||||
type: 'GET',
|
||||
dataType: 'json',
|
||||
success(data) {
|
||||
$.each(data.my_modules, (index, myModule) => {
|
||||
$(`div.panel[data-module-id='${myModule.id}']`)
|
||||
.find('.edit-tags-link')
|
||||
.html(myModule.tags_html);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
// Canvas
|
||||
if ($('#canvas-container').length) {
|
||||
$.ajax({
|
||||
url: this.myModuleUrl,
|
||||
type: 'GET',
|
||||
dataType: 'json',
|
||||
success(data) {
|
||||
$(`div.panel[data-module-id='${data.data.id}']`)
|
||||
.find('.edit-tags-link')
|
||||
.html(data.data.attributes.tags_html);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
app.component('tags-modal', TagsModal);
|
||||
app.config.globalProperties.i18n = window.I18n;
|
||||
mountWithTurbolinks(app, id);
|
||||
};
|
||||
|
||||
const tagsModalContainers = document.querySelectorAll('.vue-tags-modal:not(.initialized)');
|
||||
tagsModalContainers.forEach((container) => {
|
||||
$(container).addClass('initialized');
|
||||
window.initTagsModalComponent(`#${container.id}`);
|
||||
}
|
||||
});
|
||||
app.component('tags-modal', TagsModal);
|
||||
app.config.globalProperties.i18n = window.I18n;
|
||||
mountWithTurbolinks(app, '#tagsModalContainer');
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
position="right"
|
||||
@dtEvent="changeSort"
|
||||
btnIcon="sn-icon sn-icon-sort-down"
|
||||
:e2eSortButton="e2eSortButton"
|
||||
:dataE2e="e2eSortButton"
|
||||
></MenuDropdown>
|
||||
</template>
|
||||
|
||||
|
|
|
|||
|
|
@ -85,9 +85,14 @@
|
|||
<div :class="inRepository ? 'protocol-section protocol-information' : ''">
|
||||
<div v-if="inRepository" id="protocol-description" class="protocol-section-header">
|
||||
<div class="protocol-description-container">
|
||||
<a class="protocol-section-caret" role="button" data-toggle="collapse" href="#protocol-description-container" aria-expanded="false" aria-controls="protocol-description-container">
|
||||
<a class="protocol-section-caret"
|
||||
role="button"
|
||||
data-toggle="collapse"
|
||||
href="#protocol-description-container"
|
||||
aria-expanded="false"
|
||||
aria-controls="protocol-description-container">
|
||||
<i class="sn-icon sn-icon-right"></i>
|
||||
<span id="protocolDescriptionLabel" class="protocol-section-title">
|
||||
<span id="protocolDescriptionLabel" class="protocol-section-title" data-e2e="e2e-TX-protocolTemplates-protocolDescription-title">
|
||||
<h2>
|
||||
{{ i18n.t("protocols.header.protocol_description") }}
|
||||
</h2>
|
||||
|
|
@ -95,7 +100,10 @@
|
|||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div id="protocol-description-container" class="text-base" :class=" inRepository ? 'protocol-description collapse in' : ''" >
|
||||
<div id="protocol-description-container"
|
||||
class="text-base"
|
||||
:class=" inRepository ? 'protocol-description collapse in' : ''"
|
||||
data-e2e="e2e-IF-protocolTemplates-protocolDescription-content">
|
||||
<div v-if="urls.update_protocol_description_url">
|
||||
<Tinymce
|
||||
:value="protocol.attributes.description"
|
||||
|
|
@ -123,7 +131,7 @@
|
|||
<div class="protocol-steps-container">
|
||||
<a class="protocol-section-caret" role="button" data-toggle="collapse" href="#protocol-steps-container" aria-expanded="false" aria-controls="protocol-steps-container">
|
||||
<i class="sn-icon sn-icon-right"></i>
|
||||
<span id="protocolStepsLabel" class="protocol-section-title">
|
||||
<span id="protocolStepsLabel" class="protocol-section-title" data-e2e="e2e-TX-protocol-templateSteps-title">
|
||||
<h2>
|
||||
{{ i18n.t("protocols.header.protocol_steps") }}
|
||||
</h2>
|
||||
|
|
@ -137,6 +145,7 @@
|
|||
<a
|
||||
class="btn btn-secondary"
|
||||
:title="i18n.t('protocols.steps.new_step_title')"
|
||||
data-e2e="e2e-BT-protocol-templateSteps-newStepTop"
|
||||
@keyup.enter="addStep(steps.length)"
|
||||
@click="addStep(steps.length)"
|
||||
tabindex="0">
|
||||
|
|
@ -144,15 +153,16 @@
|
|||
<span>{{ i18n.t("protocols.steps.new_step") }}</span>
|
||||
</a>
|
||||
<div v-if="steps.length > 0" class="flex justify-between items-center gap-4">
|
||||
<button @click="collapseSteps" class="btn btn-secondary flex px-4" tabindex="0">
|
||||
<button @click="collapseSteps" class="btn btn-secondary flex px-4" tabindex="0" data-e2e="e2e-BT-protocol-templateSteps-collapse">
|
||||
{{ i18n.t("protocols.steps.collapse_label") }}
|
||||
</button>
|
||||
<button @click="expandSteps" class="btn btn-secondary flex px-4" tabindex="0">
|
||||
<button @click="expandSteps" class="btn btn-secondary flex px-4" tabindex="0" data-e2e="e2e-BT-protocol-templateSteps-expand">
|
||||
{{ i18n.t("protocols.steps.expand_label") }}
|
||||
</button>
|
||||
<a v-if="steps.length > 0 && urls.reorder_steps_url"
|
||||
class="btn btn-light icon-btn"
|
||||
data-toggle="modal"
|
||||
data-e2e="e2e-BT-protocol-templateSteps-reorder"
|
||||
@click="startStepReorder"
|
||||
@keyup.enter="startStepReorder"
|
||||
:class="{'disabled': steps.length == 1}"
|
||||
|
|
@ -163,7 +173,7 @@
|
|||
</div>
|
||||
<div class="protocol-steps pb-8">
|
||||
<div v-for="(step, index) in steps" :key="step.id" class="step-block">
|
||||
<div v-if="index > 0 && urls.add_step_url" class="insert-step" @click="addStep(index)">
|
||||
<div v-if="index > 0 && urls.add_step_url" class="insert-step" @click="addStep(index)" data-e2e="e2e-BT-protocol-templateSteps-insertStep">
|
||||
<i class="sn-icon sn-icon-new-task"></i>
|
||||
</div>
|
||||
<Step
|
||||
|
|
@ -185,7 +195,7 @@
|
|||
:reorderStepUrl="steps.length > 1 ? urls.reorder_steps_url : null"
|
||||
:assignableMyModuleId="protocol.attributes.assignable_my_module_id"
|
||||
/>
|
||||
<div v-if="(index === steps.length - 1) && urls.add_step_url" class="insert-step" @click="addStep(index + 1)">
|
||||
<div v-if="(index === steps.length - 1) && urls.add_step_url" class="insert-step" @click="addStep(index + 1)" data-e2e="e2e-BT-protocol-templateSteps-insertStep">
|
||||
<i class="sn-icon sn-icon-new-task"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -193,6 +203,7 @@
|
|||
<a
|
||||
class="btn btn-secondary"
|
||||
:title="i18n.t('protocols.steps.new_step_title')"
|
||||
data-e2e="e2e-BT-protocol-templateSteps-newStepBottom"
|
||||
@keyup.enter="addStep(steps.length)"
|
||||
@click="addStep(steps.length)"
|
||||
tabindex="0">
|
||||
|
|
|
|||
|
|
@ -1,32 +1,33 @@
|
|||
<template>
|
||||
<div class="protocol-section protocol-information mb-4">
|
||||
<div class="protocol-section protocol-information mb-4" data-e2e="e2e-CO-protocolTemplates-protocolDetails">
|
||||
<div id="protocol-details" class="protocol-section-header">
|
||||
<div class="protocol-details-container">
|
||||
<a class="protocol-section-caret" role="button" data-toggle="collapse"
|
||||
href="#details-container" aria-expanded="false" aria-controls="details-container">
|
||||
<i class="sn-icon sn-icon-right"></i>
|
||||
<span id="protocolDetailsLabel" class="protocol-section-title">
|
||||
<h2>
|
||||
<h2 data-e2e="e2e-TX-protocolTemplates-protocolDetails-title">
|
||||
{{ i18n.t("protocols.header.details") }}
|
||||
</h2>
|
||||
<span class="protocol-code" >{{ protocol.attributes.code }}</span>
|
||||
<span class="protocol-code" data-e2e="e2e-TX-protocolTemplates-protocolDetails-protocolId">{{ protocol.attributes.code }}</span>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="actions-block">
|
||||
<a class="btn btn-light icon-btn pull-right"
|
||||
:href="protocol.attributes.urls.print_protocol_url" target="_blank">
|
||||
:href="protocol.attributes.urls.print_protocol_url" target="_blank"
|
||||
data-e2e="e2e-BT-protocolTemplates-protocolDetails-print">
|
||||
<span class="sn-icon sn-icon-printer" aria-hidden="true"></span>
|
||||
</a>
|
||||
<button class="btn btn-light" @click="openVersionsModal">
|
||||
<button class="btn btn-light" @click="openVersionsModal" data-e2e="e2e-BT-protocolTemplates-protocolDetails-versions">
|
||||
{{ i18n.t("protocols.header.versions") }}
|
||||
</button>
|
||||
<button v-if="protocol.attributes.urls.publish_url"
|
||||
@click="$emit('publish')" class="btn btn-primary">
|
||||
@click="$emit('publish')" class="btn btn-primary" data-e2e="e2e-BT-protocolTemplates-protocolDetails-publish">
|
||||
{{ i18n.t("protocols.header.publish") }}</button>
|
||||
<button v-if="protocol.attributes.urls.save_as_draft_url"
|
||||
:disabled="protocol.attributes.has_draft || creatingDraft"
|
||||
@click="saveAsdraft" class="btn btn-secondary">
|
||||
@click="saveAsdraft" class="btn btn-secondary" data-e2e="e2e-BT-protocolTemplates-protocolDetails-saveAsDraft">
|
||||
{{ i18n.t("protocols.header.save_as_draft") }}
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -34,33 +35,33 @@
|
|||
<div id="details-container" class="protocol-details collapse in">
|
||||
<div class="protocol-metadata">
|
||||
<p class="data-block">
|
||||
<span>{{ i18n.t("protocols.header.version") }}</span>
|
||||
<b>{{ titleVersion }}</b>
|
||||
<span data-e2e="e2e-TX-protocolTemplates-protocolDetails-versionLabel">{{ i18n.t("protocols.header.version") }}</span>
|
||||
<b data-e2e="e2e-TX-protocolTemplates-protocolDetails-version">{{ titleVersion }}</b>
|
||||
</p>
|
||||
<p class="data-block" v-if="protocol.attributes.published">
|
||||
<span>{{ i18n.t("protocols.header.published_on") }}</span>
|
||||
<b>{{ protocol.attributes.published_on_formatted }}</b>
|
||||
<span data-e2e="e2e-TX-protocolTemplates-protocolDetails-publishedOnLabel">{{ i18n.t("protocols.header.published_on") }}</span>
|
||||
<b data-e2e="e2e-TX-protocolTemplates-protocolDetails-publishedOn">{{ protocol.attributes.published_on_formatted }}</b>
|
||||
</p>
|
||||
<p class="data-block" v-if="protocol.attributes.published">
|
||||
<p class="data-block" v-if="protocol.attributes.published" data-e2e="e2e-TX-protocolTemplates-protocolDetails-publishedBy">
|
||||
<span>{{ i18n.t("protocols.header.published_by") }}</span>
|
||||
<img :src="protocol.attributes.published_by.avatar" class="rounded-full"/>
|
||||
{{ protocol.attributes.published_by.name }}
|
||||
</p>
|
||||
<p class="data-block">
|
||||
<span>{{ i18n.t("protocols.header.updated_at") }}</span>
|
||||
<b>{{ protocol.attributes.updated_at_formatted }}</b>
|
||||
<span data-e2e="e2e-TX-protocolTemplates-protocolDetails-updatedAtLabel">{{ i18n.t("protocols.header.updated_at") }}</span>
|
||||
<b data-e2e="e2e-TX-protocolTemplates-protocolDetails-updatedAt">{{ protocol.attributes.updated_at_formatted }}</b>
|
||||
</p>
|
||||
<p class="data-block">
|
||||
<span>{{ i18n.t("protocols.header.created_at") }}</span>
|
||||
<b>{{ protocol.attributes.created_at_formatted }}</b>
|
||||
<span data-e2e="e2e-TX-protocolTemplates-protocolDetails-createdAtLabel">{{ i18n.t("protocols.header.created_at") }}</span>
|
||||
<b data-e2e="e2e-TX-protocolTemplates-protocolDetails-createdAt">{{ protocol.attributes.created_at_formatted }}</b>
|
||||
</p>
|
||||
<p class="data-block">
|
||||
<p class="data-block" data-e2e="e2e-TX-protocolTemplates-protocolDetails-createdBy">
|
||||
<span>{{ i18n.t("protocols.header.added_by") }}</span>
|
||||
<img :src="protocol.attributes.added_by.avatar" class="rounded-full"/>
|
||||
{{ protocol.attributes.added_by.name }}
|
||||
</p>
|
||||
<p class="data-block authors-data">
|
||||
<span>{{ i18n.t("protocols.header.authors") }}</span>
|
||||
<span data-e2e="e2e-TX-protocolTemplates-protocolDetails-authorsLabel">{{ i18n.t("protocols.header.authors") }}</span>
|
||||
<span class="authors-list" v-if="protocol.attributes.urls.update_protocol_authors_url">
|
||||
<InlineEdit
|
||||
:value="protocol.attributes.authors"
|
||||
|
|
@ -68,15 +69,16 @@
|
|||
:allowBlank="true"
|
||||
:attributeName="`${i18n.t('Protocol')} ${i18n.t('protocols.header.authors_list')}`"
|
||||
:characterLimit="10000"
|
||||
:dataE2e="'protocolTemplates-protocolDetails-authors'"
|
||||
@update="updateAuthors"
|
||||
/>
|
||||
</span>
|
||||
<span class="authors-list" v-else>
|
||||
<span class="authors-list" data-e2e="e2e-TX-protocolTemplates-protocolDetails-authorsPublished" v-else>
|
||||
{{ protocol.attributes.authors }}
|
||||
</span>
|
||||
</p>
|
||||
<p class="data-block keywords-data">
|
||||
<span>{{ i18n.t("protocols.header.keywords") }}</span>
|
||||
<span data-e2e="e2e-TX-protocolTemplates-protocolDetails-keywordsLabel">{{ i18n.t("protocols.header.keywords") }}</span>
|
||||
<span
|
||||
class="keywords-list"
|
||||
v-if="protocol.attributes.urls.update_protocol_authors_url || protocol.attributes.keywords.length">
|
||||
|
|
@ -90,6 +92,7 @@
|
|||
:noEmptyOption="false"
|
||||
:selectAppearance="'tag'"
|
||||
:viewMode="protocol.attributes.urls.update_protocol_keywords_url == null"
|
||||
:dataE2e="'protocolTemplates-protocolDetails-keywords'"
|
||||
@dropdown:changed="updateKeywords"
|
||||
/>
|
||||
</span>
|
||||
|
|
@ -117,7 +120,7 @@ export default {
|
|||
protocol: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
@dragover.prevent
|
||||
:data-id="step.id"
|
||||
:class="{ 'draging-file': dragingFile, 'editing-name': editingName, 'locked': !urls.update_url, 'pointer-events-none': addingContent }"
|
||||
:data-e2e="`e2e-CO-protocol-step${step.id}`"
|
||||
>
|
||||
<div class="drop-message" @dragleave.prevent="!showFileModal ? dragingFile = false : null">
|
||||
{{ i18n.t('protocols.steps.drop_message', { position: step.attributes.position + 1 }) }}
|
||||
|
|
@ -18,7 +19,8 @@
|
|||
:href="'#stepBody' + step.id"
|
||||
data-toggle="collapse"
|
||||
data-remote="true"
|
||||
@click="toggleCollapsed">
|
||||
@click="toggleCollapsed"
|
||||
:data-e2e="`e2e-BT-protocol-step${step.id}-toggleCollapsed`">
|
||||
<span class="sn-icon sn-icon-right "></span>
|
||||
</a>
|
||||
<div v-if="!inRepository" class="step-complete-container" :class="{ 'step-element--locked': !urls.state_url }">
|
||||
|
|
@ -27,9 +29,10 @@
|
|||
@keyup.enter="changeState"
|
||||
tabindex="0"
|
||||
:title="step.attributes.completed ? i18n.t('protocols.steps.status.uncomplete') : i18n.t('protocols.steps.status.complete')"
|
||||
:data-e2e="`e2e-BT-protocol-step${step.id}-toggleCompleted`"
|
||||
></div>
|
||||
</div>
|
||||
<div class="step-position leading-5">
|
||||
<div class="step-position leading-5" :data-e2e="`e2e-TX-protocol-step${step.id}-position`">
|
||||
{{ step.attributes.position + 1 }}.
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -48,6 +51,7 @@
|
|||
@editingEnabled="editingName = true"
|
||||
@editingDisabled="editingName = false"
|
||||
:editOnload="step.newStep == true"
|
||||
:dataE2e="`protocol-step${step.id}-title`"
|
||||
@update="updateName"
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -60,6 +64,7 @@
|
|||
:btnText="i18n.t('protocols.steps.insert.button')"
|
||||
:position="'right'"
|
||||
:caret="true"
|
||||
:dataE2e="`e2e-DD-protocol-step${step.id}-insertContent`"
|
||||
@create:table="(...args) => this.createElement('table', ...args)"
|
||||
@create:checklist="createElement('checklist')"
|
||||
@create:text="createElement('text')"
|
||||
|
|
@ -98,6 +103,7 @@
|
|||
:btnClasses="'btn btn-light icon-btn'"
|
||||
:position="'right'"
|
||||
:btnIcon="'sn-icon sn-icon-more-hori'"
|
||||
:dataE2e="`e2e-DD-protocol-step${step.id}-options`"
|
||||
@reorder="openReorderModal"
|
||||
@duplicate="duplicateStep"
|
||||
@delete="showDeleteModal"
|
||||
|
|
@ -116,6 +122,7 @@
|
|||
:reorderElementUrl="elements.length > 1 ? urls.reorder_elements_url : ''"
|
||||
:assignableMyModuleId="assignableMyModuleId"
|
||||
:isNew="element.isNew"
|
||||
:dataE2e="`protocol-step${step.id}`"
|
||||
@component:adding-content="($event) => addingContent = $event"
|
||||
@component:delete="deleteElement"
|
||||
@update="updateElement"
|
||||
|
|
@ -127,8 +134,10 @@
|
|||
:parent="step"
|
||||
:attachments="attachments"
|
||||
:attachmentsReady="attachmentsReady"
|
||||
:dataE2e="`protocol-step${step.id}`"
|
||||
@attachments:openFileModal="showFileModal = true"
|
||||
@attachment:deleted="attachmentDeleted"
|
||||
@attachment:update="updateAttachment"
|
||||
@attachment:uploaded="loadAttachments"
|
||||
@attachment:changed="reloadAttachment"
|
||||
@attachments:order="changeAttachmentsOrder"
|
||||
|
|
@ -141,6 +150,7 @@
|
|||
<ReorderableItemsModal v-if="reordering"
|
||||
:title="i18n.t('protocols.steps.modals.reorder_elements.title', { step_position: step.attributes.position + 1 })"
|
||||
:items="reorderableElements"
|
||||
:dataE2e="`e2e-BT-protocol-step${step.id}-reorder`"
|
||||
@reorder="updateElementOrder"
|
||||
@close="closeReorderModal"
|
||||
/>
|
||||
|
|
@ -211,13 +221,34 @@
|
|||
editingName: false,
|
||||
inlineEditError: null,
|
||||
wellPlateOptions: [
|
||||
{ text: I18n.t('protocols.steps.insert.well_plate_options.32_x_48'), emit: 'create:table', params: [32, 48] },
|
||||
{ text: I18n.t('protocols.steps.insert.well_plate_options.16_x_24'), emit: 'create:table', params: [16, 24] },
|
||||
{ text: I18n.t('protocols.steps.insert.well_plate_options.8_x_12'), emit: 'create:table', params: [8, 12] },
|
||||
{ text: I18n.t('protocols.steps.insert.well_plate_options.6_x_8'), emit: 'create:table', params: [6, 8] },
|
||||
{ text: I18n.t('protocols.steps.insert.well_plate_options.4_x_6'), emit: 'create:table', params: [4, 6] },
|
||||
{ text: I18n.t('protocols.steps.insert.well_plate_options.3_x_4'), emit: 'create:table', params: [3, 4]},
|
||||
{ text: I18n.t('protocols.steps.insert.well_plate_options.2_x_3'), emit: 'create:table', params: [2, 3] }
|
||||
{ text: I18n.t('protocols.steps.insert.well_plate_options.32_x_48'),
|
||||
emit: 'create:table',
|
||||
params: [32, 48],
|
||||
data_e2e: `e2e-BT-protocol-step${this.step.id}-insertWellPlate-32` },
|
||||
{ text: I18n.t('protocols.steps.insert.well_plate_options.16_x_24'),
|
||||
emit: 'create:table',
|
||||
params: [16, 24],
|
||||
data_e2e: `e2e-BT-protocol-step${this.step.id}-insertWellPlate-16` },
|
||||
{ text: I18n.t('protocols.steps.insert.well_plate_options.8_x_12'),
|
||||
emit: 'create:table',
|
||||
params: [8, 12],
|
||||
data_e2e: `e2e-BT-protocol-step${this.step.id}-insertWellPlate-8` },
|
||||
{ text: I18n.t('protocols.steps.insert.well_plate_options.6_x_8'),
|
||||
emit: 'create:table',
|
||||
params: [6, 8],
|
||||
data_e2e: `e2e-BT-protocol-step${this.step.id}-insertWellPlate-6` },
|
||||
{ text: I18n.t('protocols.steps.insert.well_plate_options.4_x_6'),
|
||||
emit: 'create:table',
|
||||
params: [4, 6],
|
||||
data_e2e: `e2e-BT-protocol-step${this.step.id}-insertWellPlate-4` },
|
||||
{ text: I18n.t('protocols.steps.insert.well_plate_options.3_x_4'),
|
||||
emit: 'create:table',
|
||||
params: [3, 4],
|
||||
data_e2e: `e2e-BT-protocol-step${this.step.id}-insertWellPlate-3` },
|
||||
{ text: I18n.t('protocols.steps.insert.well_plate_options.2_x_3'),
|
||||
emit: 'create:table',
|
||||
params: [2, 3],
|
||||
data_e2e: `e2e-BT-protocol-step${this.step.id}-insertWellPlate-2` }
|
||||
]
|
||||
}
|
||||
},
|
||||
|
|
@ -275,25 +306,29 @@
|
|||
if (this.urls.upload_attachment_url) {
|
||||
menu = menu.concat([{
|
||||
text: this.i18n.t('protocols.steps.insert.add_file'),
|
||||
emit: 'create:file'
|
||||
emit: 'create:file',
|
||||
data_e2e: `e2e-BT-protocol-step${this.step.id}-insertAttachment-file`
|
||||
}]);
|
||||
}
|
||||
if (this.step.attributes.wopi_enabled) {
|
||||
menu = menu.concat([{
|
||||
text: this.i18n.t('assets.create_wopi_file.button_text'),
|
||||
emit: 'create:wopi_file'
|
||||
emit: 'create:wopi_file',
|
||||
data_e2e: `e2e-BT-protocol-step${this.step.id}-insertAttachment-wopi`
|
||||
}]);
|
||||
}
|
||||
if (this.step.attributes.open_vector_editor_context.new_sequence_asset_url) {
|
||||
menu = menu.concat([{
|
||||
text: this.i18n.t('open_vector_editor.new_sequence_file'),
|
||||
emit: 'create:ove_file'
|
||||
emit: 'create:ove_file',
|
||||
data_e2e: `e2e-BT-protocol-step${this.step.id}-insertAttachment-sequence`
|
||||
}]);
|
||||
}
|
||||
if (this.step.attributes.marvinjs_enabled) {
|
||||
menu = menu.concat([{
|
||||
text: this.i18n.t('marvinjs.new_button'),
|
||||
emit: 'create:marvinjs_file'
|
||||
emit: 'create:marvinjs_file',
|
||||
data_e2e: `e2e-BT-protocol-step${this.step.id}-insertAttachment-chemicalDrawing`
|
||||
}]);
|
||||
}
|
||||
return menu;
|
||||
|
|
@ -303,21 +338,26 @@
|
|||
if (this.urls.update_url) {
|
||||
menu = menu.concat([{
|
||||
text: this.i18n.t('protocols.steps.insert.text'),
|
||||
emit: 'create:text'
|
||||
emit: 'create:text',
|
||||
data_e2e: `e2e-BT-protocol-step${this.step.id}-insertText`
|
||||
},{
|
||||
text: this.i18n.t('protocols.steps.insert.attachment'),
|
||||
submenu: this.filesMenu,
|
||||
position: 'left'
|
||||
position: 'left',
|
||||
data_e2e: `e2e-BT-protocol-step${this.step.id}-insertAttachment`
|
||||
},{
|
||||
text: this.i18n.t('protocols.steps.insert.table'),
|
||||
emit: 'create:table'
|
||||
emit: 'create:table',
|
||||
data_e2e: `e2e-BT-protocol-step${this.step.id}-insertTable`
|
||||
},{
|
||||
text: this.i18n.t('protocols.steps.insert.well_plate'),
|
||||
submenu: this.wellPlateOptions,
|
||||
position: 'left'
|
||||
position: 'left',
|
||||
data_e2e: `e2e-BT-protocol-step${this.step.id}-insertWellplate`
|
||||
},{
|
||||
text: this.i18n.t('protocols.steps.insert.checklist'),
|
||||
emit: 'create:checklist'
|
||||
emit: 'create:checklist',
|
||||
data_e2e: `e2e-BT-protocol-step${this.step.id}-insertChecklist`
|
||||
}]);
|
||||
}
|
||||
|
||||
|
|
@ -328,19 +368,22 @@
|
|||
if (this.urls.reorder_elements_url) {
|
||||
menu = menu.concat([{
|
||||
text: this.i18n.t('protocols.steps.options_dropdown.rearrange'),
|
||||
emit: 'reorder'
|
||||
emit: 'reorder',
|
||||
data_e2e: `e2e-BT-protocol-step${this.step.id}-stepOptions-rearrange`
|
||||
}]);
|
||||
}
|
||||
if (this.urls.duplicate_step_url) {
|
||||
menu = menu.concat([{
|
||||
text: this.i18n.t('protocols.steps.options_dropdown.duplicate'),
|
||||
emit: 'duplicate'
|
||||
emit: 'duplicate',
|
||||
data_e2e: `e2e-BT-protocol-step${this.step.id}-stepOptions-duplicate`
|
||||
}]);
|
||||
}
|
||||
if (this.urls.delete_url) {
|
||||
menu = menu.concat([{
|
||||
text: this.i18n.t('protocols.steps.options_dropdown.delete'),
|
||||
emit: 'delete'
|
||||
emit: 'delete',
|
||||
data_e2e: `e2e-BT-protocol-step${this.step.id}-stepOptions-delete`
|
||||
}]);
|
||||
}
|
||||
return menu;
|
||||
|
|
@ -550,6 +593,12 @@
|
|||
this.attachments = this.attachments.filter((a) => a.id !== id );
|
||||
this.$emit('stepUpdated');
|
||||
},
|
||||
updateAttachment(attachment) {
|
||||
const index = this.attachments.findIndex(a => a.id === attachment.id);
|
||||
if (index !== -1) {
|
||||
this.attachments[index] = attachment;
|
||||
}
|
||||
},
|
||||
closeCommentsSidebar() {
|
||||
this.showCommentsSidebar = false
|
||||
},
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@
|
|||
:attachment="attachment"
|
||||
@attachment:viewMode="updateViewMode"
|
||||
@attachment:delete="deleteAttachment"
|
||||
@attachment:update="$emit('attachment:update', $event)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -2,31 +2,32 @@
|
|||
<div ref="modal" class="modal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog" role="document">
|
||||
<form @submit.prevent="submit">
|
||||
<div class="modal-content">
|
||||
<div class="modal-content" data-e2e="e2e-MD-newProtocol">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close" data-e2e="e2e-BT-newProtocolModal-close">
|
||||
<i class="sn-icon sn-icon-close"></i>
|
||||
</button>
|
||||
<h4 class="modal-title truncate !block" id="edit-project-modal-label">
|
||||
<h4 class="modal-title truncate !block" id="edit-project-modal-label" data-e2e="e2e-TX-newProtocolModal-title">
|
||||
{{ i18n.t("protocols.new_protocol_modal.title_new") }}
|
||||
</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="mb-6">
|
||||
<label class="sci-label">{{ i18n.t("protocols.new_protocol_modal.name_label") }}</label>
|
||||
<label class="sci-label" data-e2e="e2e-TX-newProtocolModal-inputLabel">{{ i18n.t("protocols.new_protocol_modal.name_label") }}</label>
|
||||
<div class="sci-input-container-v2" :class="{'error': error}" :data-error="error">
|
||||
<input type="text" v-model="name"
|
||||
class="sci-input-field"
|
||||
autofocus="true" ref="input"
|
||||
data-e2e="e2e-IF-newProtocolModal-name"
|
||||
:placeholder="i18n.t('protocols.new_protocol_modal.name_placeholder')" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2 text-xs items-center">
|
||||
<div class="sci-checkbox-container">
|
||||
<input type="checkbox" class="sci-checkbox" v-model="visible" value="visible"/>
|
||||
<input type="checkbox" class="sci-checkbox" v-model="visible" value="visible" data-e2e="e2e-CB-newProtocolModal-grantAccess"/>
|
||||
<span class="sci-checkbox-label"></span>
|
||||
</div>
|
||||
<span v-html="i18n.t('protocols.new_protocol_modal.access_label')"></span>
|
||||
<span v-html="i18n.t('protocols.new_protocol_modal.access_label')" data-e2e="e2e-TX-newProtocolModal-grantAccess"></span>
|
||||
</div>
|
||||
<div class="mt-6" :class="{'hidden': !visible}">
|
||||
<label class="sci-label">{{ i18n.t("protocols.new_protocol_modal.role_label") }}</label>
|
||||
|
|
@ -34,8 +35,8 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ i18n.t('general.cancel') }}</button>
|
||||
<button class="btn btn-primary" type="submit" :disabled="visible && !defaultRole">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal" data-e2e="e2e-BT-newProtocolModal-cancel">{{ i18n.t('general.cancel') }}</button>
|
||||
<button class="btn btn-primary" type="submit" :disabled="visible && !defaultRole" data-e2e="e2e-BT-newProtocolModal-create">
|
||||
{{ i18n.t('protocols.new_protocol_modal.create_new') }}
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -203,7 +203,8 @@ export default {
|
|||
menuItems: [
|
||||
{
|
||||
emit: 'import_file',
|
||||
text: this.i18n.t('protocols.index.import_eln')
|
||||
text: this.i18n.t('protocols.index.import_eln'),
|
||||
data_e2e: 'e2e-BT-topToolbar-importEln'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
@ -214,13 +215,15 @@ export default {
|
|||
text: `<span>${this.i18n.t('protocols.index.import_docx')}</span>
|
||||
<span class="bg-sn-coral text-sn-white text-[8px] absolute leading-none p-1 top-px rounded-[1px] right-px">
|
||||
${this.i18n.t('protocols.index.beta')}
|
||||
</span>`
|
||||
</span>`,
|
||||
data_e2e: 'e2e-BT-topToolbar-importDocx'
|
||||
});
|
||||
}
|
||||
|
||||
importMenu.menuItems.push({
|
||||
emit: 'import_protocols_io',
|
||||
text: this.i18n.t('protocols.index.import_protocols_io')
|
||||
text: this.i18n.t('protocols.index.import_protocols_io'),
|
||||
data_e2e: 'e2e-BT-topToolbar-importProtocolsIo'
|
||||
});
|
||||
|
||||
left.push(importMenu);
|
||||
|
|
|
|||
|
|
@ -255,6 +255,7 @@ export default {
|
|||
});
|
||||
},
|
||||
initZebraPrinter() {
|
||||
this.printers = this.printers.filter((printer) => !printer.id.startsWith('zebra'));
|
||||
this.zebraPrinters = zebraPrint.init($('#LabelPrinterSelector'), {
|
||||
clearSelectorOnFirstDevice: false,
|
||||
appendDevice: (device) => {
|
||||
|
|
|
|||
|
|
@ -124,6 +124,7 @@
|
|||
:attachmentsReady="attachmentsReady"
|
||||
@attachments:openFileModal="showFileModal = true"
|
||||
@attachment:deleted="attachmentDeleted"
|
||||
@attachment:update="updateAttachment"
|
||||
@attachment:uploaded="loadAttachments"
|
||||
@attachment:moved="moveAttachment"
|
||||
@attachments:order="changeAttachmentsOrder"
|
||||
|
|
@ -429,6 +430,12 @@ export default {
|
|||
this.attachments = this.attachments.filter((a) => a.id !== id);
|
||||
this.$emit('resultUpdated');
|
||||
},
|
||||
updateAttachment(attachment) {
|
||||
const index = this.attachments.findIndex((a) => a.id === attachment.id);
|
||||
if (index !== -1) {
|
||||
this.attachments[index] = attachment;
|
||||
}
|
||||
},
|
||||
createElement(elementType, tableDimensions = [5, 5], plateTemplate = false) {
|
||||
$.post(this.urls[`create_${elementType}_url`], { tableDimensions, plateTemplate }, (result) => {
|
||||
result.data.isNew = true;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
<template>
|
||||
<div class="content__attachments pr-8" :id='"content__attachments-" + parent.id'>
|
||||
<div class="content__attachments pr-8"
|
||||
:id='"content__attachments-" + parent.id'
|
||||
:data-e2e="`e2e-CO-${dataE2e}-attachments`">
|
||||
<div class="sci-divider my-6"></div>
|
||||
<div class="content__attachments-actions">
|
||||
<div class="title">
|
||||
|
|
@ -11,6 +13,7 @@
|
|||
:btnText="i18n.t('attachments.preview_menu')"
|
||||
:position="'right'"
|
||||
:caret="true"
|
||||
:data_e2e="`e2e-DD-${dataE2e}-attachments-viewOptions`"
|
||||
@attachment:viewMode = "changeAttachmentsViewMode"
|
||||
></MenuDropdown>
|
||||
<MenuDropdown
|
||||
|
|
@ -18,6 +21,7 @@
|
|||
:btnIcon="'sn-icon sn-icon-sort-down'"
|
||||
:btnClasses="'btn btn-light icon-btn'"
|
||||
:position="'right'"
|
||||
:data_e2e="`e2e-DD-${dataE2e}-attachments-orderOptions`"
|
||||
@attachment:order = "changeAttachmentsOrder"
|
||||
></MenuDropdown>
|
||||
</div>
|
||||
|
|
@ -29,11 +33,13 @@
|
|||
:is="attachment_view_mode(attachmentsOrdered[index])"
|
||||
:attachment="attachment"
|
||||
:parentId="parseInt(parent.id)"
|
||||
:dataE2e="`${dataE2e}`"
|
||||
@attachment:viewMode="updateAttachmentViewMode"
|
||||
@attachment:delete="deleteAttachment(attachment.id)"
|
||||
@attachment:moved="attachmentMoved"
|
||||
@attachment:uploaded="$emit('attachment:uploaded')"
|
||||
@attachment:changed="$emit('attachment:changed', $event)"
|
||||
@attachment:update="$emit('attachment:update', $event)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -63,6 +69,10 @@ export default {
|
|||
attachmentsReady: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
dataE2e: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
|
|
@ -118,7 +128,8 @@ export default {
|
|||
active: this.parent.attributes.assets_view_mode == viewMode,
|
||||
text: this.i18n.t(`attachments.view_mode.${viewMode}_html`),
|
||||
emit: 'attachment:viewMode',
|
||||
params: viewMode
|
||||
params: viewMode,
|
||||
data_e2e: `e2e-BT-${this.dataE2e}-viewOptions-${viewMode}`
|
||||
});
|
||||
});
|
||||
return menu;
|
||||
|
|
@ -130,7 +141,8 @@ export default {
|
|||
text: this.i18n.t(`general.sort_new.${orderOption}`),
|
||||
emit: 'attachment:order',
|
||||
params: orderOption,
|
||||
active: this.parent.attributes.assets_order === orderOption
|
||||
active: this.parent.attributes.assets_order === orderOption,
|
||||
data_e2e: `e2e-BT-${this.dataE2e}-orderOptions-${orderOption}`
|
||||
});
|
||||
});
|
||||
return menu;
|
||||
|
|
|
|||
|
|
@ -35,11 +35,19 @@
|
|||
@open_scinote_editor="openScinoteEditor"
|
||||
@open_locally="openLocally"
|
||||
@delete="deleteModal = true"
|
||||
@rename="renameModal = true"
|
||||
@duplicate="duplicate"
|
||||
@viewMode="changeViewMode"
|
||||
@move="showMoveModal"
|
||||
@menu-visibility-changed="$emit('menu-visibility-changed', $event)"
|
||||
></MenuDropdown>
|
||||
<Teleport to="body">
|
||||
<RenameAttachmentModal
|
||||
v-if="renameModal"
|
||||
:attachment="attachment"
|
||||
@attachment:update="$emit('attachment:update', $event)"
|
||||
@close="renameModal = false"
|
||||
/>
|
||||
<deleteAttachmentModal
|
||||
v-if="deleteModal"
|
||||
:fileName="attachment.attributes.file_name"
|
||||
|
|
@ -72,15 +80,18 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import RenameAttachmentModal from '../modal/rename_modal.vue';
|
||||
import deleteAttachmentModal from './delete_modal.vue';
|
||||
import moveAssetModal from '../modal/move.vue';
|
||||
import MoveMixin from './mixins/move.js';
|
||||
import OpenLocallyMixin from './mixins/open_locally.js';
|
||||
import MenuDropdown from '../../menu_dropdown.vue';
|
||||
import axios from '../../../../packs/custom_axios.js';
|
||||
|
||||
export default {
|
||||
name: 'contextMenu',
|
||||
components: {
|
||||
RenameAttachmentModal,
|
||||
deleteAttachmentModal,
|
||||
moveAssetModal,
|
||||
MenuDropdown
|
||||
|
|
@ -96,7 +107,8 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
viewModeOptions: ['inline', 'thumbnail', 'list'],
|
||||
deleteModal: false
|
||||
deleteModal: false,
|
||||
renameModal: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -157,6 +169,18 @@ export default {
|
|||
data_e2e: 'e2e-BT-attachmentOptions-move'
|
||||
});
|
||||
}
|
||||
if (this.attachment.attributes.urls.duplicate) {
|
||||
menu.push({
|
||||
text: this.i18n.t('assets.context_menu.duplicate'),
|
||||
emit: 'duplicate'
|
||||
});
|
||||
}
|
||||
if (this.attachment.attributes.urls.rename) {
|
||||
menu.push({
|
||||
text: this.i18n.t('assets.context_menu.rename'),
|
||||
emit: 'rename'
|
||||
});
|
||||
}
|
||||
if (this.attachment.attributes.urls.delete) {
|
||||
menu.push({
|
||||
text: this.i18n.t('assets.context_menu.delete'),
|
||||
|
|
@ -189,6 +213,13 @@ export default {
|
|||
data: { asset: { view_mode: viewMode } }
|
||||
});
|
||||
},
|
||||
duplicate() {
|
||||
axios.post(this.attachment.attributes.urls.duplicate).then(() => {
|
||||
this.reloadAttachments();
|
||||
}).catch((e) => {
|
||||
console.error(e);
|
||||
});
|
||||
},
|
||||
deleteAttachment() {
|
||||
this.deleteModal = false;
|
||||
this.$emit('attachment:delete');
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
<template>
|
||||
<div class="attachment-container asset" :data-asset-id="attachment.id">
|
||||
<div class="attachment-container asset"
|
||||
:data-asset-id="attachment.id"
|
||||
:data-e2e="`e2e-CO-${dataE2e}-attachment${attachment.id}-empty`">
|
||||
<div
|
||||
class="file-name"
|
||||
:id="`modal_link${attachment.id}`"
|
||||
|
|
@ -23,6 +25,10 @@ export default {
|
|||
parentId: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
dataE2e: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
<template>
|
||||
<div class="inline-attachment-container asset"
|
||||
:data-asset-id="attachment.id"
|
||||
:data-e2e="`e2e-CO-${dataE2e}-attachment${attachment.id}-inline`"
|
||||
>
|
||||
<div class="header">
|
||||
<div class="file-info">
|
||||
|
|
@ -34,6 +35,7 @@
|
|||
@attachment:delete="deleteAttachment"
|
||||
@attachment:moved="attachmentMoved"
|
||||
@attachment:uploaded="reloadAttachments"
|
||||
@attachment:update="$emit('attachment:update', $event)"
|
||||
/>
|
||||
</div>
|
||||
<template v-if="attachment.attributes.wopi">
|
||||
|
|
@ -85,6 +87,10 @@ export default {
|
|||
parentId: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
dataE2e: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
<template>
|
||||
<div class="list-attachment-container asset"
|
||||
:data-asset-id="attachment.id"
|
||||
:data-e2e="`e2e-CO-${dataE2e}-attachment${attachment.id}-list`"
|
||||
>
|
||||
<i class="text-sn-grey asset-icon sn-icon" :class="attachment.attributes.icon"></i>
|
||||
<a :href="attachment.attributes.urls.blob"
|
||||
|
|
@ -35,6 +36,7 @@
|
|||
@attachment:delete="deleteAttachment"
|
||||
@attachment:moved="attachmentMoved"
|
||||
@attachment:uploaded="reloadAttachments"
|
||||
@attachment:update="$emit('attachment:update', $event)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -56,6 +58,10 @@ export default {
|
|||
parentId: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
dataE2e: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
@mouseenter="handleMouseEnter"
|
||||
@mouseleave="handleMouseLeave"
|
||||
v-click-outside="handleClickOutsideThumbnail"
|
||||
:data-e2e="`e2e-CO-${dataE2e}-attachment${attachment.id}-thumbnail`"
|
||||
>
|
||||
<a :class="{ hidden: showOptions }"
|
||||
:href="attachment.attributes.urls.blob"
|
||||
|
|
@ -101,7 +102,10 @@
|
|||
>
|
||||
<i class="sn-icon sn-icon-open"></i>
|
||||
</a>
|
||||
<a v-if="attachment.attributes.urls.move" @click.prevent.stop="showMoveModal" class="btn btn-light icon-btn thumbnail-action-btn" :title="i18n.t('attachments.thumbnail.buttons.move')">
|
||||
<a v-if="attachment.attributes.urls.move"
|
||||
@click.prevent.stop="showMoveModal"
|
||||
class="btn btn-light icon-btn thumbnail-action-btn"
|
||||
:title="i18n.t('attachments.thumbnail.buttons.move')">
|
||||
<i class="sn-icon sn-icon-move"></i>
|
||||
</a>
|
||||
<a class="btn btn-light icon-btn thumbnail-action-btn"
|
||||
|
|
@ -126,6 +130,7 @@
|
|||
@attachment:moved="attachmentMoved"
|
||||
@attachment:uploaded="reloadAttachments"
|
||||
@attachment:changed="$emit('attachment:changed', $event)"
|
||||
@attachment:update="$emit('attachment:update', $event)"
|
||||
@menu-visibility-changed="handleMenuVisibilityChange"
|
||||
:withBorder="true"
|
||||
/>
|
||||
|
|
@ -201,6 +206,10 @@ export default {
|
|||
parentId: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
dataE2e: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
|
|
@ -243,7 +252,7 @@ export default {
|
|||
});
|
||||
}
|
||||
return options;
|
||||
},
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
$(this.$nextTick(() => {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
<template>
|
||||
<div class="content__checklist-container pr-8" >
|
||||
<div class="content__checklist-container pr-8" :data-e2e="`e2e-CO-${dataE2e}-checklist${element.id}`">
|
||||
<div class="sci-divider my-6" v-if="!inRepository"></div>
|
||||
<div class="checklist-header flex rounded mb-1 items-center relative w-full group/checklist-header" :class="{ 'editing-name': editingName, 'locked': !element.attributes.orderable.urls.update_url }">
|
||||
<div class="checklist-header flex rounded mb-1 items-center relative w-full group/checklist-header"
|
||||
:class="{ 'editing-name': editingName, 'locked': !element.attributes.orderable.urls.update_url }">
|
||||
<div class="grow-1 text-ellipsis whitespace-nowrap grow my-1 font-bold">
|
||||
<InlineEdit
|
||||
:class="{ 'pointer-events-none': !element.attributes.orderable.urls.update_url }"
|
||||
|
|
@ -13,6 +14,7 @@
|
|||
:autofocus="editingName"
|
||||
:smartAnnotation="true"
|
||||
:attributeName="`${i18n.t('Checklist')} ${i18n.t('name')}`"
|
||||
:dataE2e="`${dataE2e}-checklist${element.id}`"
|
||||
@editingEnabled="editingName = true"
|
||||
@editingDisabled="editingName = false"
|
||||
@update="updateName"
|
||||
|
|
@ -24,6 +26,7 @@
|
|||
:btnClasses="'btn btn-light icon-btn btn-sm'"
|
||||
:position="'right'"
|
||||
:btnIcon="'sn-icon sn-icon-more-hori'"
|
||||
:dataE2e="`e2e-DD-${dataE2e}-checklist${element.id}-options`"
|
||||
@edit="editingName = true"
|
||||
@duplicate="duplicateElement"
|
||||
@move="showMoveModal"
|
||||
|
|
@ -51,6 +54,7 @@
|
|||
:reorderChecklistItemUrl="this.element.attributes.orderable.urls.reorder_url"
|
||||
:inRepository="inRepository"
|
||||
:draggable="checklistItems.length > 1"
|
||||
:data-e2e="`${dataE2e}-checklistItem${element.id}`"
|
||||
@editStart="editingItem = true"
|
||||
@editEnd="editingItem = false"
|
||||
@update="saveItem"
|
||||
|
|
@ -63,6 +67,7 @@
|
|||
<div v-if="element.attributes.orderable.urls.create_item_url && !addingNewItem"
|
||||
class="flex items-center gap-1 text-sn-blue cursor-pointer mb-2 mt-1 "
|
||||
tabindex="0"
|
||||
:data-e2e="`e2e-BT-${dataE2e}-checklist${element.id}-addNew`"
|
||||
@keyup.enter="addItem(checklistItems[checklistItems.length - 1]?.id)"
|
||||
@click="addItem(checklistItems[checklistItems.length - 1]?.id)">
|
||||
<i class="sn-icon sn-icon-new-task w-6 text-center inline-block"></i>
|
||||
|
|
@ -120,6 +125,10 @@ export default {
|
|||
assignableMyModuleId: {
|
||||
type: Number,
|
||||
required: false
|
||||
},
|
||||
dataE2e: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
|
|
@ -154,25 +163,29 @@ export default {
|
|||
if (this.element.attributes.orderable.urls.update_url) {
|
||||
menu.push({
|
||||
text: I18n.t('general.edit'),
|
||||
emit: 'edit'
|
||||
emit: 'edit',
|
||||
data_e2e: `e2e-BT-${this.dataE2e}-checklist${this.element.id}-options-edit`
|
||||
});
|
||||
}
|
||||
if (this.element.attributes.orderable.urls.duplicate_url) {
|
||||
menu.push({
|
||||
text: I18n.t('general.duplicate'),
|
||||
emit: 'duplicate'
|
||||
emit: 'duplicate',
|
||||
data_e2e: `e2e-BT-${this.dataE2e}-checklist${this.element.id}-options-duplicate`
|
||||
});
|
||||
}
|
||||
if (this.element.attributes.orderable.urls.move_targets_url) {
|
||||
menu.push({
|
||||
text: I18n.t('general.move'),
|
||||
emit: 'move'
|
||||
emit: 'move',
|
||||
data_e2e: `e2e-BT-${this.dataE2e}-checklist${this.element.id}-options-move`
|
||||
});
|
||||
}
|
||||
if (this.element.attributes.orderable.urls.delete_url) {
|
||||
menu.push({
|
||||
text: I18n.t('general.delete'),
|
||||
emit: 'delete'
|
||||
emit: 'delete',
|
||||
data_e2e: `e2e-BT-${this.dataE2e}-checklist${this.element.id}-options-delete`
|
||||
});
|
||||
}
|
||||
return menu;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="content__checklist-item pl-10 ml-[-2.325rem] group/checklist-item-header">
|
||||
<div class="content__checklist-item pl-10 ml-[-2.325rem] group/checklist-item-header" :data-e2e="`e2e-CO-${dataE2e}`">
|
||||
<div class="checklist-item-header flex rounded items-center relative w-full" :class="{ 'locked': locked || editingText, 'editing-name': editingText }">
|
||||
<div v-if="reorderChecklistItemUrl"
|
||||
class="absolute h-6 cursor-grab justify-center left-[-2.325rem] top-0.5 px-2 tw-hidden text-sn-grey element-grip step-element-grip--draggable"
|
||||
|
|
@ -14,7 +14,9 @@
|
|||
type="checkbox"
|
||||
class="sci-checkbox"
|
||||
:disabled="checklistItem.attributes.isNew"
|
||||
:checked="checklistItem.attributes.checked" @change="toggleChecked($event)" />
|
||||
:checked="checklistItem.attributes.checked"
|
||||
:data-e2e="`e2e-CB-${dataE2e}-toggleChecked`"
|
||||
@change="toggleChecked($event)" />
|
||||
<span class="sci-checkbox-label" >
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -37,6 +39,7 @@
|
|||
:editOnload="checklistItem.attributes.isNew"
|
||||
:smartAnnotation="true"
|
||||
:allowNewLine="true"
|
||||
:dataE2e="dataE2e"
|
||||
@editingEnabled="enableTextEdit"
|
||||
@editingDisabled="disableTextEdit"
|
||||
@update="updateText"
|
||||
|
|
@ -46,6 +49,7 @@
|
|||
/>
|
||||
<span v-if="!editingText && (!checklistItem.attributes.urls || deleteUrl)"
|
||||
class="absolute right-0 top-0.5 leading-6 tw-hidden group-hover/checklist-item-header:inline-block !text-sn-blue cursor-pointer"
|
||||
:data-e2e="`e2e-BT-${dataE2e}-delete`"
|
||||
@click="showDeleteModal" tabindex="0">
|
||||
<i class="sn-icon sn-icon-delete"></i>
|
||||
</span>
|
||||
|
|
@ -88,6 +92,10 @@ export default {
|
|||
reordering: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
dataE2e: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
|
|
|
|||
98
app/javascript/vue/shared/content/modal/rename_modal.vue
Normal file
98
app/javascript/vue/shared/content/modal/rename_modal.vue
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
<template>
|
||||
<div ref="modal" @keydown.esc="close" class="modal" id="renameAttachmentModal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog modal-sm" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><i class="sn-icon sn-icon-close"></i></button>
|
||||
<h4 class="modal-title">
|
||||
{{ i18n.t('assets.rename_modal.title') }}
|
||||
</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<label class="sci-label">
|
||||
{{ i18n.t('assets.from_clipboard.file_name')}}
|
||||
</label>
|
||||
<div class="flex gap-1 items-center">
|
||||
<div class="sci-input-container" :class="{ 'error': error }" :data-error-text="error">
|
||||
<input ref="input" v-model="name" type="text" class="sci-input-field" @keyup.enter="renameAttachment(name)" required="true" />
|
||||
</div>
|
||||
<div v-if="this.attachment.attributes.file_extension" class="shrink-0">.{{ this.attachment.attributes.file_extension }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-secondary" @click="close">{{ i18n.t('general.cancel') }}</button>
|
||||
<button class="btn btn-primary" @click="renameAttachment(name)" :disabled="error">{{ this.i18n.t('assets.context_menu.rename') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import modalMixin from '../../modal_mixin';
|
||||
import axios from '../../../../packs/custom_axios.js';
|
||||
|
||||
export default {
|
||||
name: 'RenameAttachmentModal',
|
||||
mixins: [modalMixin],
|
||||
props: {
|
||||
attachment: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
name: null,
|
||||
error: null
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.name = this.attachment.attributes.file_name_without_extension;
|
||||
},
|
||||
watch: {
|
||||
name() {
|
||||
this.validateName();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
validateName() {
|
||||
if (!this.name || this.name.length === 0) {
|
||||
this.error = this.i18n.t('assets.rename_modal.min_length_error');
|
||||
} else if (this.name.length > 255) {
|
||||
this.error = this.i18n.t('assets.rename_modal.max_length_error');
|
||||
} else {
|
||||
this.error = null;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
async renameAttachment(newName) {
|
||||
if (!this.validateName()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let fileName = '';
|
||||
|
||||
if (this.attachment.attributes.file_extension) {
|
||||
fileName = `${newName}.${this.attachment.attributes.file_extension}`;
|
||||
} else {
|
||||
fileName = newName;
|
||||
}
|
||||
|
||||
const payload = { asset: { name: fileName } };
|
||||
try {
|
||||
const response = await axios.patch(this.attachment.attributes.urls.rename, payload);
|
||||
this.$emit('attachment:update', response.data.data);
|
||||
this.close();
|
||||
} catch (error) {
|
||||
if (error.response) {
|
||||
this.error = error.response.data.errors || error.response.statusText;
|
||||
} else {
|
||||
this.error = error.message;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
<template>
|
||||
<div class="content__table-container pr-8">
|
||||
<div class="content__table-container pr-8"
|
||||
:data-e2e="`e2e-CO-${dataE2e}-${element.attributes.orderable.metadata.plateTemplate ? 'wellPlate' : 'table'}${element.id}`">
|
||||
<div class="sci-divider my-6" v-if="!inRepository"></div>
|
||||
<div class="table-header h-9 flex rounded mb-3 items-center relative w-full group/table-header" :class="{ 'editing-name': editingName, 'locked': locked }">
|
||||
<div v-if="!locked || element.attributes.orderable.name" :key="reloadHeader"
|
||||
|
|
@ -12,6 +13,7 @@
|
|||
:allowBlank="false"
|
||||
:autofocus="editingName"
|
||||
:attributeName="`${i18n.t('Table')} ${i18n.t('name')}`"
|
||||
:dataE2e="`${dataE2e}-${element.attributes.orderable.metadata.plateTemplate ? 'wellPlate' : 'table'}${element.id}`"
|
||||
@editingEnabled="enableNameEdit"
|
||||
@editingDisabled="disableNameEdit"
|
||||
@update="updateName"
|
||||
|
|
@ -23,6 +25,7 @@
|
|||
:btnClasses="'btn btn-light icon-btn btn-sm'"
|
||||
:position="'right'"
|
||||
:btnIcon="'sn-icon sn-icon-more-hori'"
|
||||
:dataE2e="`e2e-DD-${dataE2e}-${element.attributes.orderable.metadata.plateTemplate ? 'wellPlate' : 'table'}${element.id}-options`"
|
||||
@edit="enableNameEdit"
|
||||
@duplicate="duplicateElement"
|
||||
@move="showMoveModal"
|
||||
|
|
@ -32,11 +35,14 @@
|
|||
<div class="table-body group/table-body relative border-solid border-transparent"
|
||||
:class="{'edit border-sn-light-grey': editingTable, 'view': !editingTable, 'locked': !element.attributes.orderable.urls.update_url}"
|
||||
tabindex="0"
|
||||
:data_e2e="`e2e-TB-${dataE2e}-${element.attributes.orderable.metadata.plateTemplate ? 'wellPlate' : 'table'}${element.id}`"
|
||||
@keyup.enter="!editingTable && enableTableEdit()">
|
||||
<div ref="hotTable" class="hot-table-container" @click="!editingTable && enableTableEdit()">
|
||||
</div>
|
||||
<div class="text-xs pt-3 pb-2 text-sn-grey h-1">
|
||||
<span v-if="editingTable">{{ i18n.t('protocols.steps.table.edit_message') }}</span>
|
||||
<span v-if="editingTable" :dataE2e="`e2e-TX-${dataE2e}-${element.attributes.orderable.metadata.plateTemplate ? 'wellPlate' : 'table'}${element.id}-editMessage`">
|
||||
{{ i18n.t('protocols.steps.table.edit_message') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<deleteElementModal v-if="confirmingDelete" @confirm="deleteElement" @cancel="closeDeleteModal"/>
|
||||
|
|
@ -82,6 +88,10 @@ export default {
|
|||
assignableMyModuleId: {
|
||||
type: Number,
|
||||
required: false
|
||||
},
|
||||
dataE2e: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
|
|
@ -104,25 +114,29 @@ export default {
|
|||
if (this.element.attributes.orderable.urls.update_url) {
|
||||
menu.push({
|
||||
text: I18n.t('general.edit'),
|
||||
emit: 'edit'
|
||||
emit: 'edit',
|
||||
data_e2e: `e2e-BT-${this.dataE2e}-${this.element.attributes.orderable.metadata.plateTemplate ? 'wellPlate' : 'table'}${this.element.id}-options-edit`
|
||||
});
|
||||
}
|
||||
if (this.element.attributes.orderable.urls.duplicate_url) {
|
||||
menu.push({
|
||||
text: I18n.t('general.duplicate'),
|
||||
emit: 'duplicate'
|
||||
emit: 'duplicate',
|
||||
data_e2e: `e2e-BT-${this.dataE2e}-${this.element.attributes.orderable.metadata.plateTemplate ? 'wellPlate' : 'table'}${this.element.id}-options-duplicate`
|
||||
});
|
||||
}
|
||||
if (this.element.attributes.orderable.urls.move_targets_url) {
|
||||
menu.push({
|
||||
text: I18n.t('general.move'),
|
||||
emit: 'move'
|
||||
emit: 'move',
|
||||
data_e2e: `e2e-BT-${this.dataE2e}-${this.element.attributes.orderable.metadata.plateTemplate ? 'wellPlate' : 'table'}${this.element.id}-options-move`
|
||||
});
|
||||
}
|
||||
if (this.element.attributes.orderable.urls.delete_url) {
|
||||
menu.push({
|
||||
text: I18n.t('general.delete'),
|
||||
emit: 'delete'
|
||||
emit: 'delete',
|
||||
data_e2e: `e2e-BT-${this.dataE2e}-${this.element.attributes.orderable.metadata.plateTemplate ? 'wellPlate' : 'table'}${this.element.id}-options-delete`
|
||||
});
|
||||
}
|
||||
return menu;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
<template>
|
||||
<div class="content__text-container pr-8">
|
||||
<div class="content__text-container pr-8" :data-e2e="`e2e-CO-${dataE2e}-stepText${element.id}`">
|
||||
<div class="sci-divider my-6" v-if="!inRepository"></div>
|
||||
<div class="text-header h-9 flex rounded mb-1 items-center relative w-full group/text-header" :class="{ 'editing-name': editingName, 'locked': !element.attributes.orderable.urls.update_url }">
|
||||
<div class="text-header h-9 flex rounded mb-1 items-center relative w-full group/text-header"
|
||||
:class="{ 'editing-name': editingName,
|
||||
'locked': !element.attributes.orderable.urls.update_url }">
|
||||
<div v-if="element.attributes.orderable.urls.update_url || element.attributes.orderable.name"
|
||||
class="grow-1 text-ellipsis whitespace-nowrap grow my-1 font-bold"
|
||||
:class="{'pointer-events-none': !element.attributes.orderable.urls.update_url}"
|
||||
|
|
@ -13,6 +15,7 @@
|
|||
:allowBlank="true"
|
||||
:autofocus="editingName"
|
||||
:attributeName="`${i18n.t('Text')} ${i18n.t('name')}`"
|
||||
:dataE2e="`${dataE2e}-stepText${element.id}`"
|
||||
@editingEnabled="enableNameEdit"
|
||||
@editingDisabled="disableNameEdit"
|
||||
@update="updateName"
|
||||
|
|
@ -24,13 +27,18 @@
|
|||
:btnClasses="'btn btn-light icon-btn btn-sm'"
|
||||
:position="'right'"
|
||||
:btnIcon="'sn-icon sn-icon-more-hori'"
|
||||
:dataE2e="`e2e-DD-${dataE2e}-stepText${element.id}-options`"
|
||||
@edit="enableNameEdit"
|
||||
@duplicate="duplicateElement"
|
||||
@move="showMoveModal"
|
||||
@delete="showDeleteModal"
|
||||
></MenuDropdown>
|
||||
</div>
|
||||
<div class="flex rounded min-h-[2.25rem] mb-4 relative group/text_container content__text-body" :class="{ 'edit': inEditMode, 'component__element--locked': !element.attributes.orderable.urls.update_url }" @keyup.enter="enableEditMode($event)" tabindex="0">
|
||||
<div class="flex rounded min-h-[2.25rem] mb-4 relative group/text_container content__text-body"
|
||||
:class="{ 'edit': inEditMode, 'component__element--locked': !element.attributes.orderable.urls.update_url }"
|
||||
:data-e2e="`e2e-IF-${dataE2e}-stepText${element.id}`"
|
||||
@keyup.enter="enableEditMode($event)"
|
||||
tabindex="0">
|
||||
<Tinymce
|
||||
v-if="element.attributes.orderable.urls.update_url"
|
||||
:value="element.attributes.orderable.text"
|
||||
|
|
@ -48,8 +56,8 @@
|
|||
@editingDisabled="disableEditMode"
|
||||
@editingEnabled="enableEditMode"
|
||||
/>
|
||||
<div class="view-text-element" v-else-if="element.attributes.orderable.text_view" v-html="wrappedTables"></div>
|
||||
<div v-else class="text-sn-grey">
|
||||
<div class="view-text-element" v-else-if="element.attributes.orderable.text_view" v-html="wrappedTables" :data-e2e="`e2e-TX-${dataE2e}-stepText${element.id}`"></div>
|
||||
<div v-else class="text-sn-grey" :data-e2e="`e2e-TX-${dataE2e}-stepText${element.id}-empty`">
|
||||
{{ i18n.t("protocols.steps.text.empty_text") }}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -96,6 +104,10 @@ export default {
|
|||
assignableMyModuleId: {
|
||||
type: Number,
|
||||
required: false
|
||||
},
|
||||
dataE2e: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
|
|
@ -126,25 +138,29 @@ export default {
|
|||
if (this.element.attributes.orderable.urls.update_url) {
|
||||
menu.push({
|
||||
text: I18n.t('general.edit'),
|
||||
emit: 'edit'
|
||||
emit: 'edit',
|
||||
data_e2e: `e2e-BT-${this.dataE2e}-stepText${this.element.id}-options-edit`
|
||||
});
|
||||
}
|
||||
if (this.element.attributes.orderable.urls.duplicate_url) {
|
||||
menu.push({
|
||||
text: I18n.t('general.duplicate'),
|
||||
emit: 'duplicate'
|
||||
emit: 'duplicate',
|
||||
data_e2e: `e2e-BT-${this.dataE2e}-stepText${this.element.id}-options-duplicate`
|
||||
});
|
||||
}
|
||||
if (this.element.attributes.orderable.urls.move_targets_url) {
|
||||
menu.push({
|
||||
text: I18n.t('general.move'),
|
||||
emit: 'move'
|
||||
emit: 'move',
|
||||
data_e2e: `e2e-BT-${this.dataE2e}-stepText${this.element.id}-options-move`
|
||||
});
|
||||
}
|
||||
if (this.element.attributes.orderable.urls.delete_url) {
|
||||
menu.push({
|
||||
text: I18n.t('general.delete'),
|
||||
emit: 'delete'
|
||||
emit: 'delete',
|
||||
data_e2e: `e2e-BT-${this.dataE2e}-stepText${this.element.id}-options-delete`
|
||||
});
|
||||
}
|
||||
return menu;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div v-if="pages.length > 1" class="flex gap-3 select-none">
|
||||
<div class="w-9 h-9">
|
||||
<div class="w-9 h-9 cursor-pointer flex items-center justify-center"
|
||||
<div class="w-9 h-9 cursor-pointer flex items-center justify-center" data-e2e="e2e-BT-tableInfo-left"
|
||||
@click="$emit('setPage', currentPage - 1)"
|
||||
v-if="currentPage > 1">
|
||||
<i class="sn-icon sn-icon-left cursor-pointer"></i>
|
||||
|
|
@ -11,11 +11,12 @@
|
|||
v-for="page in pages"
|
||||
:class="{ 'border-solid rounded border-sn-science-blue': page === currentPage }"
|
||||
:key="page"
|
||||
:data-e2e="`e2e-BT-tableInfo-page-${page}`"
|
||||
@click="$emit('setPage', page)">
|
||||
<span >{{ page }}</span>
|
||||
</div>
|
||||
<div class="w-9 h-9">
|
||||
<div class="w-9 h-9 cursor-pointer flex items-center justify-center"
|
||||
<div class="w-9 h-9 cursor-pointer flex items-center justify-center" data-e2e="e2e-BT-tableInfo-right"
|
||||
@click="$emit('setPage', currentPage + 1)"
|
||||
v-if="totalPage > currentPage">
|
||||
<i class="sn-icon sn-icon-right cursor-pointer"></i>
|
||||
|
|
@ -30,12 +31,12 @@ export default {
|
|||
props: {
|
||||
totalPage: {
|
||||
type: Number,
|
||||
required: true,
|
||||
required: true
|
||||
},
|
||||
currentPage: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
required: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
pages() {
|
||||
|
|
@ -50,7 +51,7 @@ export default {
|
|||
}
|
||||
}
|
||||
return pages;
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -75,17 +75,18 @@
|
|||
:params="actionsParams"
|
||||
@toolbar:action="emitAction" />
|
||||
</div>
|
||||
<div v-if="scrollMode == 'pages'" class="flex items-center py-4" :class="{'opacity-0': initializing }">
|
||||
<div class="flex items-center gap-4">
|
||||
<div v-if="scrollMode == 'pages'" class="flex items-center py-4" :class="{'opacity-0': initializing }" data-e2e="e2e-CO-tableInfo">
|
||||
<div class="flex items-center gap-4" data-e2e="e2e-TX-tableInfo-show">
|
||||
{{ i18n.t('datatable.show') }}
|
||||
<div class="w-36">
|
||||
<SelectDropdown
|
||||
:value="perPage"
|
||||
:options="perPageOptions"
|
||||
:data-e2e="'e2e-DD-tableInfo-rows'"
|
||||
@change="setPerPage"
|
||||
></SelectDropdown>
|
||||
</div>
|
||||
<div v-show="!dataLoading">
|
||||
<div v-show="!dataLoading" data-e2e="e2e-TX-tableInfo-entries">
|
||||
<span v-if="selectedRows.length">
|
||||
{{ i18n.t('datatable.entries.selected', { count: totalEntries, selected: selectedRows.length }) }}
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
:btnIcon="action.icon"
|
||||
:caret="true"
|
||||
:position="'right'"
|
||||
:data-e2e="`e2e-BT-topToolbar-${action.name}`"
|
||||
@dtEvent="handleEvent"
|
||||
></MenuDropdown>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
'border-b-sn-science-blue': !error,
|
||||
}"
|
||||
v-model="newValue"
|
||||
:data-e2e="`e2e-IF-${dataE2e}`"
|
||||
@keydown="handleKeypress"
|
||||
@blur="handleBlur"
|
||||
@keyup.escape="cancelEdit && this.atWhoOpened"
|
||||
|
|
@ -27,6 +28,7 @@
|
|||
}"
|
||||
:placeholder="placeholder"
|
||||
v-model="newValue"
|
||||
:data-e2e="`e2e-IF-${dataE2e}`"
|
||||
@keydown="handleKeypress"
|
||||
@blur="handleBlur"
|
||||
@keyup.escape="cancelEdit && this.atWhoOpened"
|
||||
|
|
@ -38,6 +40,7 @@
|
|||
ref="view"
|
||||
class="grid sci-cursor-edit leading-5 border-0 outline-none border-solid border-y border-transparent"
|
||||
:class="{ 'text-sn-grey font-normal': isBlank, 'whitespace-pre-line py-1': !singleLine }"
|
||||
:data-e2e="`e2e-TX-${dataE2e}`"
|
||||
@click="enableEdit($event)"
|
||||
>
|
||||
<span :class="{'truncate': singleLine }" :title="sa_value || placeholder" v-if="smartAnnotation" v-html="sa_value || placeholder" ></span>
|
||||
|
|
@ -48,6 +51,7 @@
|
|||
class="mt-2 whitespace-nowrap truncate text-xs font-normal absolute bottom-[-1rem] w-full"
|
||||
:title="editing && error ? error : timestamp"
|
||||
:class="{'text-sn-delete-red': editing && error}"
|
||||
:data-e2e="`e2e-TX-${dataE2e}-timestampError`"
|
||||
>
|
||||
{{ editing && error ? error : timestamp }}
|
||||
</div>
|
||||
|
|
@ -76,7 +80,8 @@ export default {
|
|||
editOnload: { type: Boolean, default: false },
|
||||
defaultValue: { type: String, default: '' },
|
||||
singleLine: { type: Boolean, default: true },
|
||||
preventLeavingUntilFilled: { type: Boolean, default: false }
|
||||
preventLeavingUntilFilled: { type: Boolean, default: false },
|
||||
dataE2e: { type: String, default: '' }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="dropdown-selector">
|
||||
<div class="dropdown-selector" :data-e2e="`e2e-IF-${dataE2e}`">
|
||||
<select :id="this.selectorId"
|
||||
:data-select-by-group="groupSelector"
|
||||
:data-combine-tags="dataCombineTags"
|
||||
|
|
@ -111,6 +111,10 @@ export default {
|
|||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
dataE2e: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
onChange: Function
|
||||
|
||||
},
|
||||
|
|
@ -127,6 +131,7 @@ export default {
|
|||
tagLabel: this.tagLabel,
|
||||
labelHTML: this.labelHTML,
|
||||
onOpen: this.onOpen,
|
||||
dataE2e: this.dataE2e,
|
||||
onChange: () => {
|
||||
if (this.onChange) this.onChange();
|
||||
this.selectChanged(dropdownSelector.getValues(`#${this.selectorId}`));
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div class="relative" v-if="listItems.length > 0 || alwaysShow" v-click-outside="closeMenu" >
|
||||
<button ref="field" :class="btnClasses" :title="title" @click="isOpen = !isOpen" :data-e2e="e2eSortButton">
|
||||
<button ref="field" :class="btnClasses" :title="title" @click="isOpen = !isOpen" :data-e2e="dataE2e">
|
||||
<i v-if="btnIcon" :class="btnIcon"></i>
|
||||
{{ btnText }}
|
||||
<i v-if="caret && isOpen" class="sn-icon sn-icon-up"></i>
|
||||
|
|
@ -35,6 +35,7 @@
|
|||
:class="{ 'bg-sn-super-light-blue': item.active }"
|
||||
class="flex group items-center rounded relative text-sn-blue whitespace-nowrap px-3 py-2.5 hover:no-underline cursor-pointer
|
||||
group-hover:bg-sn-super-light-blue hover:!bg-sn-super-light-grey"
|
||||
:data-e2e="item.data_e2e"
|
||||
>
|
||||
{{ item.text }}
|
||||
<i class="sn-icon sn-icon-right ml-auto"></i>
|
||||
|
|
@ -50,6 +51,7 @@
|
|||
:href="sub_item.url"
|
||||
:traget="sub_item.url_target || '_self'"
|
||||
:class="{ 'bg-sn-super-light-blue': item.active }"
|
||||
:data-e2e="`${sub_item.data_e2e}`"
|
||||
class="block whitespace-nowrap rounded px-3 py-2.5 hover:!text-sn-blue hover:no-underline cursor-pointer hover:bg-sn-super-light-grey leading-5"
|
||||
@click="handleClick($event, sub_item)"
|
||||
>
|
||||
|
|
@ -80,7 +82,7 @@ export default {
|
|||
caret: { type: Boolean, default: false },
|
||||
alwaysShow: { type: Boolean, default: false },
|
||||
title: { type: String, default: '' },
|
||||
e2eSortButton: { type: String, default: '' }
|
||||
dataE2e: { type: String, default: '' }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -141,8 +141,10 @@ class Asset < ApplicationRecord
|
|||
file&.blob&.content_type
|
||||
end
|
||||
|
||||
def duplicate
|
||||
def duplicate(new_name: nil)
|
||||
new_asset = dup
|
||||
file.filename = new_name if new_name
|
||||
|
||||
return unless new_asset.save
|
||||
|
||||
duplicate_file(new_asset)
|
||||
|
|
@ -155,7 +157,11 @@ class Asset < ApplicationRecord
|
|||
raise ArgumentError, 'Destination asset should be persisted first!' unless to_asset.persisted?
|
||||
|
||||
file.blob.open do |tmp_file|
|
||||
to_blob = ActiveStorage::Blob.create_and_upload!(io: tmp_file, filename: blob.filename)
|
||||
to_blob = ActiveStorage::Blob.create_and_upload!(
|
||||
io: tmp_file,
|
||||
filename: blob.filename,
|
||||
metadata: blob.metadata
|
||||
)
|
||||
to_asset.file.attach(to_blob)
|
||||
end
|
||||
|
||||
|
|
@ -352,6 +358,36 @@ class Asset < ApplicationRecord
|
|||
step || result || repository_cell
|
||||
end
|
||||
|
||||
def rename_file(new_name)
|
||||
if file.attached?
|
||||
asset_type = file.metadata['asset_type']
|
||||
new_filename = case asset_type
|
||||
when 'marvinjs'
|
||||
"#{new_name}.jpg"
|
||||
when 'gene_sequence'
|
||||
"#{new_name}.json"
|
||||
else
|
||||
new_name
|
||||
end
|
||||
|
||||
updated_metadata = file.blob.metadata.merge('name' => new_name)
|
||||
|
||||
if %w(marvinjs gene_sequence).include?(asset_type)
|
||||
file.blob.update!(
|
||||
filename: new_filename,
|
||||
metadata: updated_metadata
|
||||
)
|
||||
else
|
||||
file.blob.update!(filename: new_filename)
|
||||
end
|
||||
|
||||
if asset_type == 'gene_sequence' && preview_image.attached?
|
||||
new_image_filename = "#{new_name}.png"
|
||||
preview_image.blob.update!(filename: new_image_filename)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def tempdir
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ class AssetSyncToken < ApplicationRecord
|
|||
validates :token, uniqueness: true, presence: true
|
||||
|
||||
def version_token
|
||||
asset.file.checksum
|
||||
OpenSSL::Digest::SHA256.base64digest(asset.file.checksum + asset.file_name)
|
||||
end
|
||||
|
||||
def token_valid?
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
module SearchableModel
|
||||
extend ActiveSupport::Concern
|
||||
DATA_VECTOR_ATTRIBUTES = ['asset_text_data.data_vector', 'tables.data_vector'].freeze
|
||||
|
||||
included do
|
||||
# Helper function for relations that
|
||||
|
|
@ -108,8 +109,6 @@ module SearchableModel
|
|||
|
||||
extract_phrases(query).each_with_index do |phrase, index|
|
||||
if options[:with_subquery]
|
||||
phrase[:query] = "\"#{phrase[:query]}\"" if phrase[:exact_match]
|
||||
|
||||
subquery_result = if phrase[:negate]
|
||||
options[:raw_input].where.not(id: search_subquery(phrase[:query], options[:raw_input]))
|
||||
else
|
||||
|
|
@ -124,8 +123,8 @@ module SearchableModel
|
|||
end
|
||||
else
|
||||
phrase[:current_operator] = '' if index.zero?
|
||||
create_query_clause(normalized_attrs, index, phrase[:negate], query_clauses, value_hash,
|
||||
phrase[:query], phrase[:exact_match], phrase[:current_operator])
|
||||
create_query_clause(normalized_attrs, index, phrase[:negate], query_clauses,
|
||||
value_hash, phrase[:query], phrase[:current_operator])
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -155,8 +154,7 @@ module SearchableModel
|
|||
current_operator = ''
|
||||
|
||||
query.scan(/"[^"]+"|\S+/) do |phrase|
|
||||
phrase = Regexp.escape(phrase).gsub('\ ', ' ')
|
||||
phrase = sanitize_sql_like(phrase.strip)
|
||||
phrase = phrase.to_s.strip
|
||||
|
||||
case phrase.downcase
|
||||
when *%w(and or)
|
||||
|
|
@ -164,11 +162,8 @@ module SearchableModel
|
|||
when 'not'
|
||||
negate = true
|
||||
else
|
||||
exact_match = phrase =~ /^".*"$/
|
||||
phrase = phrase[1..-2] if exact_match
|
||||
extracted_phrases << { query: phrase,
|
||||
negate: negate,
|
||||
exact_match: exact_match,
|
||||
current_operator: current_operator.presence || 'and' }
|
||||
current_operator = ''
|
||||
negate = false
|
||||
|
|
@ -178,20 +173,21 @@ module SearchableModel
|
|||
extracted_phrases
|
||||
end
|
||||
|
||||
def self.create_query_clause(attrs, index, negate, query_clauses, value_hash, phrase, exact_match, current_operator)
|
||||
def self.create_query_clause(attrs, index, negate, query_clauses, value_hash, phrase, current_operator)
|
||||
phrase = sanitize_sql_like(phrase)
|
||||
exact_match = phrase =~ /^".*"$/
|
||||
like = exact_match ? '~' : 'ILIKE'
|
||||
phrase = exact_match ? "\\m#{phrase}\\M" : "%#{phrase}%"
|
||||
|
||||
where_clause = (attrs.map.with_index do |a, i|
|
||||
where_clause = (attrs.map.with_index do |attribute, i|
|
||||
i = (index * attrs.count) + i
|
||||
if %w(repository_rows.id repository_number_values.data).include?(a)
|
||||
"#{a} IS NOT NULL AND (((#{a})::text) #{like} :t#{i}) OR "
|
||||
elsif defined?(model::PREFIXED_ID_SQL) && a == model::PREFIXED_ID_SQL
|
||||
"#{a} IS NOT NULL AND (#{a} #{like} :t#{i}) OR "
|
||||
elsif ['asset_text_data.data_vector', 'tables.data_vector'].include?(a)
|
||||
"#{a} @@ plainto_tsquery(:t#{i}) OR "
|
||||
if %w(repository_rows.id repository_number_values.data).include?(attribute)
|
||||
"#{attribute} IS NOT NULL AND (((#{attribute})::text) #{like} :t#{i}) OR "
|
||||
elsif defined?(model::PREFIXED_ID_SQL) && attribute == model::PREFIXED_ID_SQL
|
||||
"#{attribute} IS NOT NULL AND (#{attribute} #{like} :t#{i}) OR "
|
||||
elsif DATA_VECTOR_ATTRIBUTES.include?(attribute)
|
||||
"#{attribute} @@ to_tsquery(:t#{i}) OR "
|
||||
else
|
||||
"#{a} IS NOT NULL AND ((trim_html_tags(#{a})) #{like} :t#{i}) OR "
|
||||
"#{attribute} IS NOT NULL AND ((trim_html_tags(#{attribute})) #{like} :t#{i}) OR "
|
||||
end
|
||||
end).join[0..-5]
|
||||
|
||||
|
|
@ -202,9 +198,20 @@ module SearchableModel
|
|||
end
|
||||
|
||||
value_hash.merge!(
|
||||
(attrs.map.with_index do |_, i|
|
||||
(attrs.map.with_index do |attribute, i|
|
||||
i = (index * attrs.count) + i
|
||||
["t#{i}".to_sym, phrase]
|
||||
|
||||
new_phrase = exact_match ? phrase[1..-2] : phrase
|
||||
if DATA_VECTOR_ATTRIBUTES.include?(attribute)
|
||||
new_phrase = Regexp.escape(new_phrase.gsub(/[!()&|:<]/, ' ').strip).split(/\s+/)
|
||||
new_phrase.map! { |t| "#{t}:*" } unless exact_match
|
||||
new_phrase = new_phrase.join('&').tr('\'', '"')
|
||||
else
|
||||
new_phrase = Regexp.escape(new_phrase)
|
||||
new_phrase = exact_match ? "(^|\\s)#{new_phrase}(\\s|$)" : "%#{new_phrase}%"
|
||||
end
|
||||
|
||||
["t#{i}".to_sym, new_phrase]
|
||||
end).to_h
|
||||
)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ class MyModule < ApplicationRecord
|
|||
belongs_to :changing_from_my_module_status, optional: true, class_name: 'MyModuleStatus'
|
||||
delegate :my_module_status_flow, to: :my_module_status, allow_nil: true
|
||||
has_many :results, inverse_of: :my_module, dependent: :destroy
|
||||
has_many :results_include_discarded, -> { with_discarded }, class_name: 'Result', inverse_of: :my_module
|
||||
has_many :my_module_tags, inverse_of: :my_module, dependent: :destroy
|
||||
has_many :tags, through: :my_module_tags, dependent: :destroy
|
||||
has_many :task_comments, foreign_key: :associated_id, dependent: :destroy
|
||||
|
|
@ -153,6 +154,14 @@ class MyModule < ApplicationRecord
|
|||
experiment
|
||||
end
|
||||
|
||||
def results_count(view_mode = 'active')
|
||||
return results.size if archived_branch?
|
||||
|
||||
return results.archived.size if view_mode == 'archived'
|
||||
|
||||
results.active.size
|
||||
end
|
||||
|
||||
def navigable?
|
||||
!experiment.archived? && experiment.navigable?
|
||||
end
|
||||
|
|
|
|||
|
|
@ -5,6 +5,9 @@ class Result < ApplicationRecord
|
|||
include SearchableModel
|
||||
include SearchableByNameModel
|
||||
include ViewableModel
|
||||
include Discard::Model
|
||||
|
||||
default_scope -> { kept }
|
||||
|
||||
auto_strip_attributes :name, nullify: false
|
||||
validates :name, length: { maximum: Constants::NAME_MAX_LENGTH }
|
||||
|
|
|
|||
|
|
@ -509,6 +509,22 @@ class User < ApplicationRecord
|
|||
.find_by(user_identities: { provider: provider_conf['provider_name'], uid: token_payload[:sub] })
|
||||
end
|
||||
|
||||
def self.from_api_key(api_key)
|
||||
where('api_key_expires_at > ?', Time.current).find_by(api_key: api_key)
|
||||
end
|
||||
|
||||
def regenerate_api_key!
|
||||
update!(
|
||||
api_key: SecureRandom.urlsafe_base64(33),
|
||||
api_key_created_at: Time.current,
|
||||
api_key_expires_at: Constants::API_KEY_EXPIRES_IN.from_now
|
||||
)
|
||||
end
|
||||
|
||||
def revoke_api_key!
|
||||
update!(api_key: nil, api_key_expires_at: nil)
|
||||
end
|
||||
|
||||
def has_linked_account?(provider)
|
||||
user_identities.exists?(provider: provider)
|
||||
end
|
||||
|
|
@ -614,6 +630,10 @@ class User < ApplicationRecord
|
|||
%w(id due_date age results status archived assigned tags comments)
|
||||
end
|
||||
|
||||
def api_key_enabled?
|
||||
Rails.configuration.x.core_api_key_enabled
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def confirmation_required?
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ class AssetSerializer < ActiveModel::Serializer
|
|||
include ApplicationHelper
|
||||
|
||||
attributes :file_name, :file_extension, :view_mode, :icon, :urls, :updated_at_formatted,
|
||||
:file_size, :medium_preview, :large_preview, :asset_type, :wopi,
|
||||
:file_size, :medium_preview, :large_preview, :asset_type, :wopi, :file_name_without_extension,
|
||||
:wopi_context, :pdf_previewable, :file_size_formatted, :asset_order,
|
||||
:updated_at, :metadata, :image_editable, :image_context, :pdf, :attached, :parent_type,
|
||||
:edit_version_range
|
||||
|
|
@ -23,6 +23,10 @@ class AssetSerializer < ActiveModel::Serializer
|
|||
object.render_file_name
|
||||
end
|
||||
|
||||
def file_name_without_extension
|
||||
File.basename(object.file_name, '.*')
|
||||
end
|
||||
|
||||
def file_extension
|
||||
File.extname(object.file_name)[1..]
|
||||
end
|
||||
|
|
@ -144,10 +148,13 @@ class AssetSerializer < ActiveModel::Serializer
|
|||
marvin_js_start_edit: start_editing_marvin_js_asset_path(object),
|
||||
start_edit_image: start_edit_image_path(object),
|
||||
delete: asset_destroy_path(object),
|
||||
duplicate: asset_duplicate_path(object),
|
||||
move_targets: asset_move_tagets_path(object),
|
||||
move: asset_move_path(object)
|
||||
move: asset_move_path(object),
|
||||
rename: asset_rename_path(object)
|
||||
)
|
||||
end
|
||||
|
||||
urls[:open_vector_editor_edit] = edit_gene_sequence_asset_path(object.id) if can_manage_asset?(user, object)
|
||||
|
||||
if can_manage_asset?(user, object) && can_open_asset_locally?(user, object)
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ module Lists
|
|||
status
|
||||
designated_users
|
||||
tags
|
||||
tags_html
|
||||
comments
|
||||
due_date_formatted
|
||||
permissions
|
||||
|
|
@ -142,6 +143,17 @@ module Lists
|
|||
end
|
||||
end
|
||||
|
||||
def tags_html
|
||||
# legacy canvas support
|
||||
return '' unless @instance_options[:controller]
|
||||
|
||||
@instance_options[:controller].render_to_string(
|
||||
partial: 'canvas/tags',
|
||||
locals: { my_module: object },
|
||||
formats: :html
|
||||
)
|
||||
end
|
||||
|
||||
def comments
|
||||
@user = scope[:user] || @instance_options[:user]
|
||||
{
|
||||
|
|
|
|||
|
|
@ -55,9 +55,20 @@ class ActivitiesService
|
|||
child_model = parent_model.reflect_on_association(child).class_name.to_sym
|
||||
next if subjects[child_model]
|
||||
|
||||
subjects[child_model] = parent_model.where(id: subjects[subject_name])
|
||||
.joins(child)
|
||||
.pluck("#{child.to_s.pluralize}.id")
|
||||
|
||||
if subject_name == 'Result'
|
||||
parent_model = parent_model.with_discarded
|
||||
end
|
||||
|
||||
if child == :results
|
||||
subjects[child_model] = parent_model.where(id: subjects[subject_name])
|
||||
.joins(:results_include_discarded)
|
||||
.pluck('results.id')
|
||||
else
|
||||
subjects[child_model] = parent_model.where(id: subjects[subject_name])
|
||||
.joins(child)
|
||||
.pluck("#{child.to_s.pluralize}.id")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -10,14 +10,14 @@ module Lists
|
|||
original_without_versions = @raw_data
|
||||
.where.missing(:published_versions)
|
||||
.in_repository_published_original
|
||||
.select(:id)
|
||||
.select('protocols.id')
|
||||
published_versions = @raw_data
|
||||
.in_repository_published_version
|
||||
.order(:parent_id, version_number: :desc)
|
||||
.select('DISTINCT ON (parent_id) id')
|
||||
.select('DISTINCT ON (protocols.parent_id) protocols.id')
|
||||
new_drafts = @raw_data
|
||||
.where(protocol_type: Protocol.protocol_types[:in_repository_draft], parent_id: nil)
|
||||
.select(:id)
|
||||
.select('protocols.id')
|
||||
|
||||
@records = Protocol.where('protocols.id IN (?) OR protocols.id IN (?) OR protocols.id IN (?)',
|
||||
original_without_versions, published_versions, new_drafts)
|
||||
|
|
|
|||
|
|
@ -45,6 +45,19 @@ class MarvinJsService
|
|||
asset
|
||||
end
|
||||
|
||||
def update_file_name(new_name, asset_id, current_user, current_team)
|
||||
asset = current_team.assets.find(asset_id)
|
||||
prepared_name = prepare_name(new_name)
|
||||
|
||||
ActiveRecord::Base.transaction do
|
||||
asset.last_modified_by = current_user
|
||||
asset.rename_file(prepared_name)
|
||||
asset.save!
|
||||
end
|
||||
|
||||
asset
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def connect_asset(asset, params, current_user)
|
||||
|
|
|
|||
|
|
@ -10,7 +10,8 @@
|
|||
data-module-y="<%= my_module.y %>"
|
||||
data-module-conns="<%= construct_module_connections(my_module) %>"
|
||||
data-module-users-tab-url="<%= designated_users_my_module_user_my_modules_url(my_module_id: my_module.id, format: :json) %>"
|
||||
data-module-tags-url="<%= my_module_tags_experiment_path(my_module.experiment, format: :json) %>">
|
||||
data-module-tags-url="<%= my_module_tags_experiment_path(my_module.experiment, format: :json) %>"
|
||||
data-module-url="<%= my_module_path(my_module, format: :json) %>">
|
||||
|
||||
<div data-view-mode="active">
|
||||
<a class="edit-tags-link pull-right" data-remote="true" href="<%= my_module_tags_edit_url(my_module, format: :json) %>">
|
||||
|
|
@ -114,7 +115,4 @@
|
|||
<div role="tabpanel" class="tab-pane" id="<%= my_module.id %>_comments" data-contents="comments"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%= render partial: 'canvas/full_zoom/tags_modal', locals: { my_module: my_module }%>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,25 +1,14 @@
|
|||
<div id="tagsModalContainer-<%= my_module.id %>" class="vue-tags-modal">
|
||||
<div ref="tagsModal" class="tags-modal-component" id="tagsModalComponent-<%= my_module.id %>"></div>
|
||||
<teleport to="body">
|
||||
<tags-modal v-if="tagsModalOpen"
|
||||
:params="<%=
|
||||
{
|
||||
id: my_module.id,
|
||||
permissions: {
|
||||
manage_tags: can_manage_my_module_tags?(my_module)
|
||||
},
|
||||
urls: {
|
||||
assigned_tags: assigned_tags_my_module_my_module_tags_path(my_module),
|
||||
assign_tags: my_module_my_module_tags_path(my_module)
|
||||
}
|
||||
}.to_json
|
||||
%>"
|
||||
:tags-colors="<%= Constants::TAG_COLORS.to_json %>"
|
||||
project-name="<%= my_module.experiment.project.name %>"
|
||||
project-tags-url="<%= project_tags_path(my_module.experiment.project) %>"
|
||||
@close="close"
|
||||
@tags-loaded="syncTags"
|
||||
/>
|
||||
</teleport>
|
||||
</div>
|
||||
<%= javascript_include_tag 'vue_legacy_tags_modal' %>
|
||||
<div id="tagsModalContainer" class="vue-tags-modal">
|
||||
<div ref="tagsModal" class="tags-modal-component" id="tagsModalComponent"></div>
|
||||
<teleport to="body">
|
||||
<tags-modal v-if="tagsModalOpen"
|
||||
:tags-colors="<%= Constants::TAG_COLORS.to_json %>"
|
||||
:params="myModuleParams"
|
||||
project-name="<%= @project.name %>"
|
||||
project-tags-url="<%= project_tags_path(@project) %>"
|
||||
@close="close"
|
||||
@tags-loaded="syncTags"
|
||||
/>
|
||||
</teleport>
|
||||
</div>
|
||||
<%= javascript_include_tag 'vue_legacy_tags_modal' %>
|
||||
|
|
|
|||
|
|
@ -75,6 +75,7 @@
|
|||
</div>
|
||||
|
||||
<!-- Manage tags modal -->
|
||||
<%= render partial: 'canvas/full_zoom/tags_modal' %>
|
||||
<%= render partial: "my_modules/modals/manage_module_tags_modal", locals: { my_module: nil } %>
|
||||
<%= javascript_include_tag("my_modules/tags") %>
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
data-e2e="e2e-BT-taskTabs-results"
|
||||
>
|
||||
<%= t("nav2.modules.results") %>
|
||||
<%= "[#{@my_module.results_count(params[:view_mode])}]" %>
|
||||
</a>
|
||||
<a class="p-3 border-b-4 border-transparent hover:no-underline uppercase text-bold capitalize <%= is_module_activities? ? "text-sn-blue" : "text-sn-grey" %>"
|
||||
href="<%= activities_my_module_url(@my_module, view_mode: params[:view_mode]) %>"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<div class="select-container">
|
||||
<div class="edit-button-container hidden">
|
||||
<a class="edit-tags-link" data-remote="true" href="<%= my_module_tags_edit_path(@my_module, format: :json) %>">
|
||||
<a class="edit-tags-link" data-remote="true" href="<%= my_module_path(my_module, format: :json) %>">
|
||||
<i class="sn-icon sn-icon-settings"></i>
|
||||
<span class="hidden-xs"><%= t("my_modules.details.manage_tags") %></span>
|
||||
</a>
|
||||
|
|
|
|||
|
|
@ -117,6 +117,7 @@
|
|||
data-protocol-url="<%= protocol_my_module_path(@my_module) %>"
|
||||
data-date-format="<%= datetime_picker_format_date_only %>"
|
||||
data-user-utc-offset="<%= ActiveSupport::TimeZone.find_tzinfo(current_user.time_zone).utc_offset %>"
|
||||
data-e2e="e2e-CO-task-protocol"
|
||||
>
|
||||
<protocol-container
|
||||
:protocol-url="protocolUrl"
|
||||
|
|
|
|||
|
|
@ -1,29 +1,45 @@
|
|||
<%= form_for :protocol, url: team_import_external_protocol_path(team_id: current_team.id),
|
||||
method: :post, data: { remote: true } do |f|%>
|
||||
<div class="general-error has-error">
|
||||
<div class="general-error has-error" data-e2e="e2e-TX-protocolTemplates-previewProtocolsIo-error">
|
||||
<span class="has-error help-block"></span>
|
||||
</div>
|
||||
|
||||
<div class="form-group sci-input-container">
|
||||
<%= f.label :name, t('protocols.import_export.import_modal.name_label') %>
|
||||
<%= f.text_field :name, class: 'form-control sci-input-field', value: protocol.name %>
|
||||
<%= f.label :name,
|
||||
t('protocols.import_export.import_modal.name_label'),
|
||||
:"data-e2e" => "e2e-TX-protocolTemplates-previewProtocolsIo-nameInput" %>
|
||||
<%= f.text_field :name,
|
||||
class: 'form-control sci-input-field',
|
||||
value: protocol.name,
|
||||
:"data-e2e" => "e2e-IF-protocolTemplates-previewProtocolsIo-nameInput" %>
|
||||
<span class="help-block"></span>
|
||||
</div>
|
||||
|
||||
<div class="form-group sci-input-container">
|
||||
<%= f.label :authors, t('protocols.import_export.import_modal.authors_label') %>
|
||||
<%= f.text_field :authors, class: 'form-control sci-input-field', value: protocol.authors %>
|
||||
<%= f.label :authors,
|
||||
t('protocols.import_export.import_modal.authors_label'),
|
||||
:"data-e2e" => "e2e-TX-protocolTemplates-previewProtocolsIo-authorsInput" %>
|
||||
<%= f.text_field :authors,
|
||||
class: 'form-control sci-input-field',
|
||||
value: protocol.authors,
|
||||
:"data-e2e" => "e2e-IF-protocolTemplates-previewProtocolsIo-authorsInput" %>
|
||||
</div>
|
||||
|
||||
<div class="import-protocol-preview-description">
|
||||
<div class="import-protocol-preview-description" data-e2e="e2e-TX-protocolTemplates-previewProtocolsIo-description">
|
||||
<%= custom_auto_link(protocol.description, simple_format: false, team: current_team) %>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-4">
|
||||
<div class="form-group">
|
||||
<%= f.label :published_on_label, t('protocols.import_export.import_modal.published_on_label')%>
|
||||
<%= f.text_field :published_on_label, value: I18n.l(protocol.published_on, format: :full), class: 'form-control', disabled: true %>
|
||||
<%= f.label :published_on_label,
|
||||
t('protocols.import_export.import_modal.published_on_label'),
|
||||
:"data-e2e" => "e2e-TX-protocolTemplates-previewProtocolsIo-publishedOnLabel" %>
|
||||
<%= f.text_field :published_on_label,
|
||||
value: I18n.l(protocol.published_on, format: :full),
|
||||
class: 'form-control',
|
||||
disabled: true,
|
||||
:'data-e2e' => "e2e-TX-protocolTemplates-previewProtocolsIo-publishedOn" %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -39,11 +55,11 @@
|
|||
|
||||
<div data-role="steps-container">
|
||||
<div class="row">
|
||||
<div class="col-xs-8">
|
||||
<div class="col-xs-8" data-e2e="e2e-TX-protocolTemplates-previewProtocolsIo-protocolSteps">
|
||||
<h2><%= t("protocols.steps.subtitle") %></h2>
|
||||
</div>
|
||||
</div>
|
||||
<div id="steps">
|
||||
<div id="steps" data-e2e="e2e-CO-protocolTemplates-previewProtocolsIo-protocolSteps">
|
||||
<% protocol.steps.sort_by{ |s| s.position }.each do |step| %>
|
||||
<%= render partial: "steps/step", locals: { step: step, steps_assets: steps_assets, preview: true, import: true } %>
|
||||
<% end %>
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
<div class="footer">
|
||||
<div class="left-section">
|
||||
<div class="default-role-container">
|
||||
<div class="sci-checkbox-container">
|
||||
<div class="sci-checkbox-container" data-e2e="e2e-CB-protocolTemplates-previewProtocolsIo-grantAccess">
|
||||
<%= check_box_tag "visibility", "visible", false, class: "sci-checkbox" %>
|
||||
<span class="sci-checkbox-label"></span>
|
||||
</div>
|
||||
<div class="default-role-description">
|
||||
<div class="default-role-description" data-e2e="e2e-TX-protocolTemplates-previewProtocolsIo-grantAccess">
|
||||
<%= t("protocols.new_protocol_modal.access_label") %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hidden" id="roleSelectWrapper">
|
||||
<div class="hidden" id="roleSelectWrapper" data-e2e="e2e-DD-protocolTemplates-previewProtocolsIo-userRole">
|
||||
<div class="sci-input-container">
|
||||
<%= label_tag :default_public_user_role_id, t("protocols.new_protocol_modal.role_label") %>
|
||||
<% default_role = UserRole.find_by(name: I18n.t('user_roles.predefined.viewer')).id %>
|
||||
|
|
@ -19,7 +19,13 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="right-section">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal"><%=t "general.cancel" %></button>
|
||||
<button type="button" class="btn btn-primary" data-action="import_protocol" data-import_type="in_repository_draft"><%=t "protocols.import_export.import_modal.import_protocols_label" %></button>
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal"
|
||||
data-e2e="e2e-BT-protocolTemplates-previewProtocolsIo-cancel">
|
||||
<%=t "general.cancel" %>
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary" data-action="import_protocol"
|
||||
data-import_type="in_repository_draft" data-e2e="e2e-BT-protocolTemplates-previewProtocolsIo-import">
|
||||
<%=t "protocols.import_export.import_modal.import_protocols_label" %>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,61 +1,101 @@
|
|||
<div id="import-protocol-modal" class="modal fade" role="dialog">
|
||||
<div id="import-protocol-modal" class="modal fade" role="dialog" data-e2e="e2e-MD-protocolTemplates-importEln">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
<h4 class="modal-title" data-role="header-import"><%= t("protocols.import_export.import_modal.title_import") %></h4>
|
||||
<h4 class="modal-title" data-role="header-import-into-protocol"><%= t("protocols.import_export.import_modal.title_import_into_protocol") %></h4>
|
||||
<button type="button" class="close" data-dismiss="modal" data-e2e="e2e-BT-protocolTemplates-importEln-close">
|
||||
×
|
||||
</button>
|
||||
<h4 class="modal-title" data-role="header-import" data-e2e="e2e-TX-protocolTemplates-importEln-title">
|
||||
<%= t("protocols.import_export.import_modal.title_import") %>
|
||||
</h4>
|
||||
<h4 class="modal-title" data-role="header-import-into-protocol">
|
||||
<%= t("protocols.import_export.import_modal.title_import_into_protocol") %>
|
||||
</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<!-- Warning message -->
|
||||
<div data-role="import-message" style="margin-bottom: 15px;">
|
||||
<div data-role="import-message"
|
||||
style="margin-bottom: 15px;"
|
||||
data-e2e="e2e-TX-protocolTemplates-importEln-warning">
|
||||
<b><%= t("protocols.import_export.import_modal.import_into_protocol_message") %></b>
|
||||
<br />
|
||||
</div>
|
||||
|
||||
<!-- General protocol info -->
|
||||
<div class="form-group sci-input-container">
|
||||
<label for="import_protocol_name"><%= t("protocols.import_export.import_modal.name_label") %></label>
|
||||
<input type="text" class="form-control sci-input-field" id="import_protocol_name">
|
||||
<label for="import_protocol_name" data-e2e="e2e-TX-protocolTemplates-importEln-nameInput">
|
||||
<%= t("protocols.import_export.import_modal.name_label") %>
|
||||
</label>
|
||||
<input type="text"
|
||||
class="form-control sci-input-field"
|
||||
id="import_protocol_name"
|
||||
data-e2e="e2e-IF-protocolTemplates-importEln-nameInput">
|
||||
</div>
|
||||
<div class="form-group sci-input-container">
|
||||
<label for="protocol_authors">
|
||||
<label for="protocol_authors" data-e2e="e2e-TX-protocolTemplates-importEln-authorsInput">
|
||||
<span class="sn-icon sn-icon-user-menu"></span> <%= t("protocols.import_export.import_modal.authors_label") %>
|
||||
</label>
|
||||
<input type="text" class="form-control sci-input-field" id="protocol_authors">
|
||||
<input type="text"
|
||||
class="form-control sci-input-field"
|
||||
id="protocol_authors"
|
||||
data-e2e="e2e-IF-protocolTemplates-importEln-authorsInput">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="import_protocol_description"><%= t("protocols.import_export.import_modal.description_label") %></label>
|
||||
<div id="import_protocol_description" class="overflow-auto" rows="2"></div>
|
||||
<label for="import_protocol_description" data-e2e="e2e-TX-protocolTemplates-importEln-descriptionLabel">
|
||||
<%= t("protocols.import_export.import_modal.description_label") %>
|
||||
</label>
|
||||
<div id="import_protocol_description"
|
||||
class="overflow-auto"
|
||||
rows="2"
|
||||
data-e2e="e2e-TX-protocolTemplates-importEln-description">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="row">
|
||||
<div class="col-xs-4">
|
||||
<label for="protocol_created_at"><%= t("protocols.import_export.import_modal.created_at_label") %></label>
|
||||
<input type="text" class="form-control" id="protocol_created_at" disabled>
|
||||
<label for="protocol_created_at" data-e2e="e2e-TX-protocolTemplates-importEln-createdAtLabel">
|
||||
<%= t("protocols.import_export.import_modal.created_at_label") %>
|
||||
</label>
|
||||
<input type="text"
|
||||
class="form-control"
|
||||
id="protocol_created_at"
|
||||
disabled
|
||||
data-e2e="e2e-TX-protocolTemplates-importEln-createdAt">
|
||||
</div>
|
||||
<div class="col-xs-4">
|
||||
<label for="protocol_updated_at"><%= t("protocols.import_export.import_modal.updated_at_label") %></label>
|
||||
<input type="text" class="form-control" id="protocol_updated_at" disabled>
|
||||
<label for="protocol_updated_at" data-e2e="e2e-TX-protocolTemplates-importEln-updatedAtLabel">
|
||||
<%= t("protocols.import_export.import_modal.updated_at_label") %>
|
||||
</label>
|
||||
<input type="text"
|
||||
class="form-control"
|
||||
id="protocol_updated_at"
|
||||
disabled
|
||||
data-e2e="e2e-TX-protocolTemplates-importEln-updatedAt">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Preview title -->
|
||||
<div>
|
||||
<h2 style="display: inline;"><%= t("protocols.import_export.import_modal.preview_title") %></h2>
|
||||
<h2 style="display: inline;" data-e2e="e2e-TX-protocolTemplates-importEln-previewTitle">
|
||||
<%= t("protocols.import_export.import_modal.preview_title") %>
|
||||
</h2>
|
||||
<h3 style="display: none;" data-role="title-position"></h3>
|
||||
</div>
|
||||
|
||||
<!-- Preview scroller -->
|
||||
<div>
|
||||
<div class="import-protocols-modal-preview-container" data-role="preview-container">
|
||||
<div class="import-protocols-modal-preview-container"
|
||||
data-role="preview-container"
|
||||
data-e2e="e2e-CO-protocolTemplates-importEln-preview">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div data-role="multiple-protocols-buttons">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal"><%= t("general.cancel") %></button>
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">
|
||||
<%= t("general.cancel") %>
|
||||
</button>
|
||||
<div class="btn-group" role="group">
|
||||
<a href="#" class="btn btn-secondary" data-action="jump-to-first-protocol"><i class="fas fa-fast-backward"></i></a>
|
||||
<a href="#" class="btn btn-secondary" data-action="jump-to-previous-protocol"><i class="fas fa-backward"></i></a>
|
||||
|
|
@ -64,18 +104,34 @@
|
|||
</div>
|
||||
<div class="btn-group" role="group">
|
||||
<div data-role="import-all">
|
||||
<button type="submit" class="btn btn-success" data-action="import-current"><%= t("protocols.import_export.import_modal.import_current") %></button>
|
||||
<button type="submit" class="btn btn-success" data-action="import-all"><%= t("protocols.import_export.import_modal.import_all") %></button>
|
||||
<button type="submit" class="btn btn-success" data-action="import-current">
|
||||
<%= t("protocols.import_export.import_modal.import_current") %>
|
||||
</button>
|
||||
<button type="submit" class="btn btn-success" data-action="import-all">
|
||||
<%= t("protocols.import_export.import_modal.import_all") %>
|
||||
</button>
|
||||
</div>
|
||||
<div data-role="import-single">
|
||||
<button type="submit" class="btn btn-success" data-action="import-current"><%= t("protocols.import_export.import_modal.import") %></button>
|
||||
<button type="submit" class="btn btn-success" data-action="import-current">
|
||||
<%= t("protocols.import_export.import_modal.import") %>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div data-role="single-protocol-buttons">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal"><%= t("general.cancel") %></button>
|
||||
<button type="button"
|
||||
class="btn btn-secondary"
|
||||
data-dismiss="modal"
|
||||
data-e2e="e2e-BT-protocolTemplates-importEln-cancel">
|
||||
<%= t("general.cancel") %>
|
||||
</button>
|
||||
<div class="btn-group" role="group">
|
||||
<button type="submit" class="btn btn-success" data-action="import-current"><%= t("protocols.import_export.import_modal.import") %></button>
|
||||
<button type="submit"
|
||||
class="btn btn-success"
|
||||
data-action="import-current"
|
||||
data-e2e="e2e-BT-protocolTemplates-importEln-load">
|
||||
<%= t("protocols.import_export.import_modal.import") %>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
<div class="content-pane flexible protocols-index <%= @type %>">
|
||||
<div class="content-header sticky-header">
|
||||
<div class="title-row">
|
||||
<div class="title-row" data-e2e="e2e-TX-protocolTemplates-title">
|
||||
<% if templates_view_mode_archived?(type: @type) %>
|
||||
<h1>
|
||||
<span><%= t('labels.archived')%></span>
|
||||
|
|
@ -24,7 +24,7 @@
|
|||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="protocols-container">
|
||||
<div class="protocols-container" data-e2e="e2e-CO-protocolTemplates">
|
||||
<div id="ProtocolsTable" class="fixed-content-body">
|
||||
<protocols-table
|
||||
ref="table"
|
||||
|
|
|
|||
|
|
@ -1,13 +1,21 @@
|
|||
<div class="modal" id="protocol-preview-modal" tabindex="-1" role="dialog" aria-labelledby="protocol-preview-modal-label">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-content" data-e2e="e2e-MD-protocolTemplates-previewProtocolsIo">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><i class="sn-icon sn-icon-close"></i></button>
|
||||
<h4 class="modal-title" id="protocol-preview-modal-label">
|
||||
<button type="button"
|
||||
class="close"
|
||||
data-dismiss="modal"
|
||||
aria-label="Close"
|
||||
data-e2e="e2e-BT-protocolTemplates-previewProtocolsIo-close">
|
||||
<i class="sn-icon sn-icon-close"></i>
|
||||
</button>
|
||||
<h4 class="modal-title"
|
||||
id="protocol-preview-modal-label"
|
||||
data-e2e="e2e-TX-protocolTemplates-previewProtocolsIo-title">
|
||||
</h4>
|
||||
</div>
|
||||
<div class="modal-body"></div>
|
||||
<div class="modal-footer"></div>
|
||||
<div class="modal-body" data-e2e="e2e-CO-protocolTemplates-previewProtocolsIo-body"></div>
|
||||
<div class="modal-footer" data-e2e="e2e-CO-protocolTemplates-previewProtocolsIo-footer"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<div class="modal" id="protocolsioModal" data-url="<%= protocolsio_protocols_path %>" tabindex="-1" role="dialog" aria-labelledby="protocolsio-modal-label">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content"></div>
|
||||
<div class="modal-content" data-e2e="e2e-MD-protocolTemplates-importProtocolsIo"></div>
|
||||
</div>
|
||||
</div>
|
||||
<%= javascript_include_tag "protocols/steps" %>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,12 @@
|
|||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><i class="sn-icon sn-icon-close"></i></button>
|
||||
<h4 class="modal-title" id="publish-results-modal-label">
|
||||
<button type="button"
|
||||
class="close"
|
||||
data-dismiss="modal"
|
||||
aria-label="Close"
|
||||
data-e2e="e2e-BT-protocolTemplates-importProtocolsIo-close">
|
||||
<i class="sn-icon sn-icon-close"></i>
|
||||
</button>
|
||||
<h4 class="modal-title" id="publish-results-modal-label" data-e2e="e2e-TX-protocolTemplates-importProtocolsIo-title">
|
||||
<%= t('protocols.index.protocolsio.title') %>
|
||||
</h4>
|
||||
</div>
|
||||
|
|
@ -20,6 +26,7 @@
|
|||
<input class='sci-input-field'
|
||||
type='text'
|
||||
name='key'
|
||||
data-e2e='e2e-IF-protocolTemplates-importProtocolsIo-search'
|
||||
placeholder="<%= t('protocols.index.protocolsio.search_bar_placeholder') %>" />
|
||||
<i class='sn-icon sn-icon-search'></i>
|
||||
</div>
|
||||
|
|
@ -27,7 +34,13 @@
|
|||
|
||||
<div class='protocol-sort'>
|
||||
<div class="dropdown sort-menu" title="<%= t("general.sort.title") %>">
|
||||
<button class="btn btn-light btn-black icon-btn" type="button" id="sortMenu" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
|
||||
<button class="btn btn-light btn-black icon-btn"
|
||||
type="button"
|
||||
id="sortMenu"
|
||||
data-toggle="dropdown"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="true"
|
||||
data-e2e="e2e-DD-protocolTemplates-importProtocolsIo-sort">
|
||||
<span><i class="sn-icon sn-icon-sort-down"></i></span>
|
||||
</button>
|
||||
<ul id="sortMenuDropdown" class="dropdown-menu sort-projects-menu dropdown-menu-right" aria-labelledby="sortMenu">
|
||||
|
|
@ -43,33 +56,39 @@
|
|||
</div>
|
||||
<% end %>
|
||||
<div class='protocol-list-side-panel'>
|
||||
<div class='row empty-text'>
|
||||
<div class='row empty-text' data-e2e="e2e-TX-protocolTemplates-importProtocolsIo-results-empty">
|
||||
<%= t('protocols.index.protocolsio.list_panel.empty_text') %>
|
||||
</div>
|
||||
|
||||
<div class='list-wrapper perfect-scrollbar'></div>
|
||||
<div class='list-wrapper perfect-scrollbar' data-e2e="e2e-CO-protocolTemplates-importProtocolsIo-results">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class='protocol-preview-panel'>
|
||||
<div class='empty-preview-panel'>
|
||||
|
||||
<div class='row'>
|
||||
<div class='text-rows protocol-preview-text'>
|
||||
<div class='text-rows protocol-preview-text'
|
||||
data-e2e="e2e-TX-protocolTemplates-importProtocolsIo-previewEmpty-title">
|
||||
<%= t('protocols.index.protocolsio.preview_panel.empty_title') %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class='row'>
|
||||
<div class='text-separator'> <hr> </div>
|
||||
<div class='text-separator' data-e2e="e2e-EL-protocolTemplates-importProtocolsIo-previewEmpty-separator">
|
||||
<hr>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class='row'>
|
||||
<div class='text-rows protocol-preview-subtext'>
|
||||
<div class='text-rows protocol-preview-subtext'
|
||||
data-e2e="e2e-TX-protocolTemplates-importProtocolsIo-previewEmpty-subText">
|
||||
<%= t('protocols.index.protocolsio.preview_panel.empty_subtext') %>
|
||||
</div>
|
||||
</div>
|
||||
<div class='row-bottom'>
|
||||
<div class='text-rows protocol-preview-subtext'>
|
||||
<div class='text-rows protocol-preview-subtext'
|
||||
data-e2e="e2e-TX-protocolTemplates-importProtocolsIo-previewEmpty-poweredBy">
|
||||
<%= t('protocols.index.protocolsio.preview_panel.powered_by') %>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -77,7 +96,7 @@
|
|||
|
||||
<div class='full-preview-panel' style='display: none;'>
|
||||
<div class='row preview-banner'>
|
||||
<div class='col-md-6 txt-holder'>
|
||||
<div class='col-md-6 txt-holder' data-e2e="e2e-TX-protocolTemplates-importProtocolsIo-preview-title">
|
||||
<span>
|
||||
<b><%= t('protocols.index.protocolsio.preview_panel.banner_text') %></b>
|
||||
</span>
|
||||
|
|
@ -85,7 +104,7 @@
|
|||
<div class='col-md-6 btn-holder'>
|
||||
</div>
|
||||
</div>
|
||||
<div class='preview-holder perfect-scrollbar'>
|
||||
<div class='preview-holder perfect-scrollbar' data-e2e="e2e-CO-protocolTemplates-importProtocolsIo-preview">
|
||||
<iframe scrolling="no" class='preview-iframe'></iframe>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -94,7 +113,17 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" data-dismiss="modal" class="btn btn-secondary"><%=t('general.cancel') %></button>
|
||||
<button type="button" class="btn btn-primary convert-protocol" disabled><%= t('protocols.index.protocolsio.convert') %></button>
|
||||
<button type="button"
|
||||
data-dismiss="modal"
|
||||
class="btn btn-secondary"
|
||||
data-e2e="e2e-BT-protocolTemplates-importProtocolsIo-cancel">
|
||||
<%=t('general.cancel') %>
|
||||
</button>
|
||||
<button type="button"
|
||||
class="btn btn-primary convert-protocol"
|
||||
disabled
|
||||
data-e2e="e2e-BT-protocolTemplates-importProtocolsIo-convert">
|
||||
<%= t('protocols.index.protocolsio.convert') %>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
<div class="content-pane protocols-show flexible with-grey-background pb-4" >
|
||||
<div class="content-header sticky-header">
|
||||
<div class="title-row">
|
||||
<h1>
|
||||
<h1 data-e2e="e2e-TX-protocolTemplates-protocol-title">
|
||||
<% if @inline_editable_title_config.present? %>
|
||||
<%= render partial: "shared/inline_editing",
|
||||
locals: {
|
||||
|
|
@ -33,6 +33,7 @@
|
|||
data-protocol-url="<%= protocol_path(@protocol) %>"
|
||||
data-date-format="<%= datetime_picker_format_date_only %>"
|
||||
data-user-utc-offset="<%= ActiveSupport::TimeZone.find_tzinfo(current_user.time_zone).utc_offset %>"
|
||||
data-e2e="e2e-CO-protocolTemplates-protocol"
|
||||
>
|
||||
<protocol-container
|
||||
:protocol-url="protocolUrl"
|
||||
|
|
|
|||
|
|
@ -35,6 +35,10 @@
|
|||
</div>
|
||||
</div>
|
||||
<%= render partial: 'users/registrations/edit_partials/2fa' %>
|
||||
|
||||
<% if current_user.api_key_enabled? %>
|
||||
<%= render partial: 'users/registrations/edit_partials/api_key' %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
<div class="mb-8">
|
||||
<h3 id="api-key"><%= t("users.registrations.edit.api_key.title") %></h3>
|
||||
<p>
|
||||
<%= t("users.registrations.edit.api_key.description") %>
|
||||
</p>
|
||||
<% if current_user.api_key %>
|
||||
<div class="api-key-display">
|
||||
<div class="form-group sci-input-container right-icon !w-1/2">
|
||||
<%= password_field_tag :api_key,
|
||||
current_user.api_key,
|
||||
name: 'api_key',
|
||||
class: 'form-control sci-input-field !text-sn-black !font-mono !cursor-text',
|
||||
disabled: 'disabled'
|
||||
%>
|
||||
<i class="sn-icon sn-icon-visibility-show show-password" style="cursor: pointer; z-index: 10"></i>
|
||||
</div>
|
||||
<% if current_user.api_key_expires_at < Time.current %>
|
||||
<p class="">
|
||||
<%= t("users.registrations.edit.api_key.expired") %>
|
||||
</p>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<div class="flex">
|
||||
<% if current_user.api_key %>
|
||||
<%= button_to t("users.registrations.edit.api_key.regenerate"), users_api_key_regenerate_path, class: "btn btn-primary mr-2" %>
|
||||
<%= button_to t("users.registrations.edit.api_key.revoke"), users_api_key_revoke_path, class: "btn btn-danger" %>
|
||||
<% else %>
|
||||
<%= button_to t("users.registrations.edit.api_key.generate"), users_api_key_regenerate_path, class: "btn btn-primary" %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -12,4 +12,6 @@ Rails.application.configure do
|
|||
config.x.core_api_v1_enabled = ENV['CORE_API_V1_ENABLED'] || false
|
||||
|
||||
config.x.core_api_v2_enabled = ENV['CORE_API_V2_ENABLED'] || false
|
||||
|
||||
config.x.core_api_key_enabled = ENV['CORE_API_KEY_ENABLED'] == 'true'
|
||||
end
|
||||
|
|
|
|||
|
|
@ -247,6 +247,8 @@ class Constants
|
|||
TWO_FACTOR_RECOVERY_CODE_COUNT = 6
|
||||
TWO_FACTOR_RECOVERY_CODE_LENGTH = 12
|
||||
|
||||
API_KEY_EXPIRES_IN = 1.year
|
||||
|
||||
#=============================================================================
|
||||
# Protocol importers
|
||||
#=============================================================================
|
||||
|
|
@ -424,7 +426,7 @@ class Constants
|
|||
# Team name for default admin user
|
||||
DEFAULT_PRIVATE_TEAM_NAME = 'My projects'.freeze
|
||||
|
||||
TEMPLATES_PROJECT_NAME = 'Templates'.freeze
|
||||
TEMPLATES_PROJECT_NAME = 'SciNote Examples'.freeze
|
||||
|
||||
# Interval time for polling status state
|
||||
FAST_STATUS_POLLING_INTERVAL = 5000
|
||||
|
|
|
|||
|
|
@ -488,16 +488,22 @@ class Extends
|
|||
inventory_item_relationships_unlinked: 298,
|
||||
edit_task_step_file_locally: 299,
|
||||
edit_protocol_template_file_locally: 300,
|
||||
edit_task_result_file_locally: 301
|
||||
edit_task_result_file_locally: 301,
|
||||
task_step_file_duplicated: 302,
|
||||
result_file_duplicated: 303,
|
||||
protocol_step_file_duplicated: 304,
|
||||
task_step_asset_renamed: 305,
|
||||
result_asset_renamed: 306,
|
||||
protocol_step_asset_renamed: 307
|
||||
}
|
||||
|
||||
ACTIVITY_GROUPS = {
|
||||
projects: [*0..7, 32, 33, 34, 95, 108, 65, 109, *158..162, 241, 242, 243],
|
||||
task_results: [23, 26, 25, 42, 24, 40, 41, 99, 110, 122, 116, 128, *246..248, *257..273, *284..291, 301],
|
||||
task_results: [23, 26, 25, 42, 24, 40, 41, 99, 110, 122, 116, 128, *246..248, *257..273, *284..291, 301, 303, 306],
|
||||
task: [8, 58, 9, 59, *10..14, 35, 36, 37, 53, 54, *60..63, 138, 139, 140, 64, 66, 106, 126, 120, 132,
|
||||
148, 166],
|
||||
task_protocol: [15, 22, 16, 18, 19, 20, 21, 17, 38, 39, 100, 111, 45, 46, 47, 121, 124, 115, 118, 127, 130, 137,
|
||||
184, 185, 188, 189, *192..203, 221, 222, 224, 225, 226, 236, *249..252, *274..278, 299],
|
||||
184, 185, 188, 189, *192..203, 221, 222, 224, 225, 226, 236, *249..252, *274..278, 299, 302, 305],
|
||||
task_inventory: [55, 56, 146, 147, 183],
|
||||
experiment: [*27..31, 57, 141, 165],
|
||||
reports: [48, 50, 49, 163, 164],
|
||||
|
|
@ -506,7 +512,7 @@ class Extends
|
|||
protocol_repository: [80, 103, 89, 87, 79, 90, 91, 88, 85, 86, 84, 81, 82,
|
||||
83, 101, 112, 123, 125, 117, 119, 129, 131, 187, 186,
|
||||
190, 191, *204..215, 220, 223, 227, 228, 229, *230..235,
|
||||
*237..240, *253..256, *279..283, 300],
|
||||
*237..240, *253..256, *279..283, 300, 304, 307],
|
||||
team: [92, 94, 93, 97, 104, 244, 245],
|
||||
label_templates: [*216..219]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
module PermissionExtends
|
||||
module TeamPermissions
|
||||
%w(
|
||||
NONE
|
||||
READ
|
||||
MANAGE
|
||||
USERS_MANAGE
|
||||
|
|
@ -17,6 +18,7 @@ module PermissionExtends
|
|||
|
||||
module ProtocolPermissions
|
||||
%w(
|
||||
NONE
|
||||
READ
|
||||
READ_ARCHIVED
|
||||
MANAGE
|
||||
|
|
@ -27,6 +29,7 @@ module PermissionExtends
|
|||
|
||||
module ReportPermissions
|
||||
%w(
|
||||
NONE
|
||||
READ
|
||||
MANAGE
|
||||
USERS_MANAGE
|
||||
|
|
@ -35,6 +38,7 @@ module PermissionExtends
|
|||
|
||||
module ProjectPermissions
|
||||
%w(
|
||||
NONE
|
||||
READ
|
||||
READ_ARCHIVED
|
||||
MANAGE
|
||||
|
|
@ -52,6 +56,7 @@ module PermissionExtends
|
|||
|
||||
module ExperimentPermissions
|
||||
%w(
|
||||
NONE
|
||||
READ
|
||||
READ_ARCHIVED
|
||||
MANAGE
|
||||
|
|
@ -65,6 +70,7 @@ module PermissionExtends
|
|||
|
||||
module MyModulePermissions
|
||||
%w(
|
||||
NONE
|
||||
READ
|
||||
READ_ARCHIVED
|
||||
ACTIVITIES_READ
|
||||
|
|
@ -106,6 +112,7 @@ module PermissionExtends
|
|||
|
||||
module RepositoryPermissions
|
||||
%w(
|
||||
NONE
|
||||
READ
|
||||
READ_ARCHIVED
|
||||
MANAGE
|
||||
|
|
@ -132,7 +139,7 @@ module PermissionExtends
|
|||
ExperimentPermissions.constants.map { |const| ExperimentPermissions.const_get(const) } +
|
||||
MyModulePermissions.constants.map { |const| MyModulePermissions.const_get(const) } +
|
||||
RepositoryPermissions.constants.map { |const| RepositoryPermissions.const_get(const) }
|
||||
)
|
||||
).reject { |p| p.end_with?("_none") }
|
||||
|
||||
NORMAL_USER_PERMISSIONS = [
|
||||
TeamPermissions::PROJECTS_CREATE,
|
||||
|
|
|
|||
|
|
@ -2825,6 +2825,15 @@ en:
|
|||
validation: "Are you sure you want to remove it?"
|
||||
cancel: "Cancel"
|
||||
remove: "Remove"
|
||||
api_key:
|
||||
title: "API Key"
|
||||
description: "Generate or revoke an API key for use with the SciNote API. Regenerating the API key will invalidate the old one."
|
||||
generate: "Generate"
|
||||
regenerate: "Regenerate"
|
||||
revoke: "Revoke"
|
||||
expired: "This key has expired!"
|
||||
generated: "API key generated!"
|
||||
revoked: "API key revoked!"
|
||||
new:
|
||||
head_title: "Sign up"
|
||||
team_name_label: "Team name"
|
||||
|
|
@ -3714,11 +3723,17 @@ en:
|
|||
edit_in_marvinjs: "Open in Marvin JS"
|
||||
context_menu:
|
||||
set_view_size: "SET PREVIEW SIZE"
|
||||
rename: "Rename"
|
||||
delete: "Delete"
|
||||
duplicate: "Duplicate"
|
||||
move: "Move"
|
||||
inline_html: "Large"
|
||||
thumbnail_html: "Thumbnail"
|
||||
list_html: "List"
|
||||
rename_modal:
|
||||
title: "Rename file"
|
||||
min_length_error: "File name must be at least 1 character long."
|
||||
max_length_error: "File name is too long (maximum number is 255 characters)."
|
||||
delete_file_modal:
|
||||
title: "Delete file"
|
||||
description_1_html: "You are about to delete <b>%{file_name}</b></p>"
|
||||
|
|
@ -4038,6 +4053,8 @@ en:
|
|||
api:
|
||||
core:
|
||||
status_ok: "Ok"
|
||||
invalid_api_key: "API key is invalid or expired"
|
||||
invalid_api_key_detail: "The API key you are using does not exist or has expired."
|
||||
expired_token: "Token is expired"
|
||||
invalid_token: "Token is invalid"
|
||||
missing_token: "Core: No token in the header"
|
||||
|
|
|
|||
|
|
@ -314,6 +314,12 @@ en:
|
|||
edit_image_on_inventory_item_html: "%{user} edited image %{asset_name} on inventory item %{repository_row} in inventory %{repository}: %{action}."
|
||||
edit_wopi_file_on_inventory_item_html: "%{user} edited Office online file %{asset_name} on inventory item %{repository_row} in inventory %{repository}: %{action}."
|
||||
export_inventory_stock_consumption_html: "%{user} exported stock consumption for inventory item(s) %{inventory_items} in inventory %{repository}."
|
||||
task_step_asset_renamed_html: "%{user} renamed file %{old_name} to %{new_name} on protocol's step <strong>%{step}</strong> on task <strong>%{my_module}</strong>."
|
||||
protocol_step_asset_renamed_html: "%{user} renamed file %{old_name} to %{new_name} on protocol's step <strong>%{step}</strong> in Protocol repository."
|
||||
result_asset_renamed_html: "%{user} renamed file %{old_name} to %{new_name} on result <strong>%{result}</strong> on task <strong>%{my_module}</strong>."
|
||||
task_step_file_duplicated_html: "%{user} duplicated file <strong>%{file}</strong> on protocol's step <strong>%{step}</strong> on task <strong>%{my_module}</strong>."
|
||||
result_file_duplicated_html: "%{user} duplicated file <strong>%{file}</strong> on result <strong>%{result}</strong> on task <strong>%{my_module}</strong>."
|
||||
protocol_step_file_duplicated_html: "%{user} duplicated file <strong>%{file}</strong> on protocol's step <strong>%{step}</strong> in Protocol repository."
|
||||
activity_name:
|
||||
create_project: "Project created"
|
||||
rename_project: "Project renamed"
|
||||
|
|
@ -585,6 +591,12 @@ en:
|
|||
edit_image_on_inventory_item: "Inventory item image edited"
|
||||
edit_wopi_file_on_inventory_item: "Inventory item wopi file edited"
|
||||
export_inventory_stock_consumption: "Inventory stock consumptions exported"
|
||||
task_step_asset_renamed: "File attachment on Task step renamed"
|
||||
protocol_step_asset_renamed: "File attachment on Protocol step renamed"
|
||||
result_asset_renamed: "File attachment on Task result renamed"
|
||||
task_step_file_duplicated: "File attachment on Task step duplicated"
|
||||
result_file_duplicated: "File attachment on Task result duplicated"
|
||||
protocol_step_file_duplicated: "File attachment on Protocol step duplicated"
|
||||
activity_group:
|
||||
projects: "Projects"
|
||||
task_results: "Task results"
|
||||
|
|
|
|||
|
|
@ -833,6 +833,8 @@ Rails.application.routes.draw do
|
|||
get 'files/:id/move_targets', to: 'assets#move_targets', as: 'asset_move_tagets'
|
||||
post 'files/:id/move', to: 'assets#move', as: 'asset_move'
|
||||
delete 'files/:id/', to: 'assets#destroy', as: 'asset_destroy'
|
||||
patch 'files/:id/rename', to: 'assets#rename', as: 'asset_rename'
|
||||
post 'files/:id/duplicate', to: 'assets#duplicate', as: 'asset_duplicate'
|
||||
post 'files/create_wopi_file',
|
||||
to: 'assets#create_wopi_file',
|
||||
as: 'create_wopi_file'
|
||||
|
|
@ -852,6 +854,9 @@ Rails.application.routes.draw do
|
|||
post 'users/2fa_enable' => 'users/registrations#two_factor_enable'
|
||||
post 'users/2fa_disable' => 'users/registrations#two_factor_disable'
|
||||
get 'users/2fa_qr_code' => 'users/registrations#two_factor_qr_code'
|
||||
|
||||
post 'users/api_key_regenerate' => 'users/registrations#regenerate_api_key'
|
||||
post 'users/api_key_revoke' => 'users/registrations#revoke_api_key'
|
||||
end
|
||||
|
||||
namespace :api, defaults: { format: 'json' } do
|
||||
|
|
|
|||
10
db/migrate/20220712110253_add_api_key_to_users.rb
Normal file
10
db/migrate/20220712110253_add_api_key_to_users.rb
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddApiKeyToUsers < ActiveRecord::Migration[6.1]
|
||||
def change
|
||||
change_table :users, bulk: true do |t|
|
||||
t.string :api_key
|
||||
t.datetime :api_key_expires_at
|
||||
end
|
||||
end
|
||||
end
|
||||
6
db/migrate/20240429070135_add_discarded_at_to_results.rb
Normal file
6
db/migrate/20240429070135_add_discarded_at_to_results.rb
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
class AddDiscardedAtToResults < ActiveRecord::Migration[7.0]
|
||||
def change
|
||||
add_column :results, :discarded_at, :datetime
|
||||
add_index :results, :discarded_at
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddApiKeyCreatedAtToUsers < ActiveRecord::Migration[7.0]
|
||||
def change
|
||||
add_column :users, :api_key_created_at, :timestamp
|
||||
end
|
||||
end
|
||||
|
|
@ -10,7 +10,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[7.0].define(version: 2024_01_18_094253) do
|
||||
ActiveRecord::Schema[7.0].define(version: 2024_04_29_070135) do
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "btree_gist"
|
||||
enable_extension "pg_trgm"
|
||||
|
|
@ -984,10 +984,12 @@ ActiveRecord::Schema[7.0].define(version: 2024_01_18_094253) do
|
|||
t.bigint "restored_by_id"
|
||||
t.datetime "restored_on", precision: nil
|
||||
t.integer "assets_view_mode", default: 0
|
||||
t.datetime "discarded_at"
|
||||
t.index "trim_html_tags((name)::text) gin_trgm_ops", name: "index_results_on_name", using: :gin
|
||||
t.index ["archived"], name: "index_results_on_archived"
|
||||
t.index ["archived_by_id"], name: "index_results_on_archived_by_id"
|
||||
t.index ["created_at"], name: "index_results_on_created_at"
|
||||
t.index ["discarded_at"], name: "index_results_on_discarded_at"
|
||||
t.index ["last_modified_by_id"], name: "index_results_on_last_modified_by_id"
|
||||
t.index ["my_module_id"], name: "index_results_on_my_module_id"
|
||||
t.index ["restored_by_id"], name: "index_results_on_restored_by_id"
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ describe AssetsController, type: :controller do
|
|||
|
||||
describe 'POST start_edit' do
|
||||
before do
|
||||
allow(controller).to receive(:check_edit_permission).and_return(true)
|
||||
allow(controller).to receive(:check_manage_permission).and_return(true)
|
||||
end
|
||||
let(:action) { post :create_start_edit_image_activity, params: params, format: :json }
|
||||
let!(:params) do
|
||||
|
|
|
|||
22
yarn.lock
22
yarn.lock
|
|
@ -2255,9 +2255,9 @@ axios@^0.21.1:
|
|||
follow-redirects "^1.14.0"
|
||||
|
||||
axios@^1.6.0:
|
||||
version "1.6.8"
|
||||
resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.8.tgz#66d294951f5d988a00e87a0ffb955316a619ea66"
|
||||
integrity sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==
|
||||
version "1.7.2"
|
||||
resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.2.tgz#b625db8a7051fbea61c35a3cbb3a1daa7b9c7621"
|
||||
integrity sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==
|
||||
dependencies:
|
||||
follow-redirects "^1.15.6"
|
||||
form-data "^4.0.0"
|
||||
|
|
@ -5384,10 +5384,10 @@ minipass@^3.0.0, minipass@^3.1.1, minipass@^3.1.6:
|
|||
dependencies:
|
||||
yallist "^4.0.0"
|
||||
|
||||
minipass@^4.0.0:
|
||||
version "4.2.5"
|
||||
resolved "https://registry.yarnpkg.com/minipass/-/minipass-4.2.5.tgz#9e0e5256f1e3513f8c34691dd68549e85b2c8ceb"
|
||||
integrity sha512-+yQl7SX3bIT83Lhb4BVorMAHVuqsskxRdlmO9kTpyukp8vsm2Sn/fUOV9xlnG8/a5JsypJzap21lz/y3FBMJ8Q==
|
||||
minipass@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d"
|
||||
integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==
|
||||
|
||||
minizlib@^2.1.1, minizlib@^2.1.2:
|
||||
version "2.1.2"
|
||||
|
|
@ -7331,13 +7331,13 @@ tar-stream@^3.1.5:
|
|||
streamx "^2.15.0"
|
||||
|
||||
tar@^6.1.11, tar@^6.1.2:
|
||||
version "6.1.13"
|
||||
resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.13.tgz#46e22529000f612180601a6fe0680e7da508847b"
|
||||
integrity sha512-jdIBIN6LTIe2jqzay/2vtYLlBHa3JF42ot3h1dW8Q0PaAG4v8rm0cvpVePtau5C6OKXGGcgO9q2AMNSWxiLqKw==
|
||||
version "6.2.1"
|
||||
resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.1.tgz#717549c541bc3c2af15751bea94b1dd068d4b03a"
|
||||
integrity sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==
|
||||
dependencies:
|
||||
chownr "^2.0.0"
|
||||
fs-minipass "^2.0.0"
|
||||
minipass "^4.0.0"
|
||||
minipass "^5.0.0"
|
||||
minizlib "^2.1.1"
|
||||
mkdirp "^1.0.3"
|
||||
yallist "^4.0.0"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue