Merge branch 'hotfix/1.36.2.1' into develop

This commit is contained in:
Oleksii Kriuchykhin 2024-09-11 11:53:16 +02:00
commit ae25f8097e
25 changed files with 329 additions and 129 deletions

View file

@ -1 +1 @@
1.36.2
1.36.2.1

View file

@ -19,5 +19,6 @@ const GLOBAL_CONSTANTS = {
ASSET_POLLING_INTERVAL: <%= Constants::ASSET_POLLING_INTERVAL %>,
ASSET_SYNC_URL: '<%= Constants::ASSET_SYNC_URL %>',
GLOBAL_SEARCH_PREVIEW_LIMIT: <%= Constants::GLOBAL_SEARCH_PREVIEW_LIMIT %>,
SEARCH_LIMIT: <%= Constants::SEARCH_LIMIT %>
SEARCH_LIMIT: <%= Constants::SEARCH_LIMIT %>,
SCINOTE_EDIT_RESTRICTED_EXTENSIONS: <%= Constants::SCINOTE_EDIT_RESTRICTED_EXTENSIONS %>
};

View file

@ -0,0 +1,14 @@
<template>
<i v-if="params.data.default" class="sn-icon sn-icon-approval"></i>
<span v-else></span>
</template>
<script>
export default {
props: {
params: {
type: Object,
required: true
}
}
};
</script>

View file

@ -0,0 +1,17 @@
<template>
<span>
<span v-html="params.data.icon_url"></span>
<span>{{ params.data.format }}</span>
</span>
</template>
<script>
export default {
props: {
params: {
type: Object,
required: true
}
}
};
</script>

View file

@ -0,0 +1,16 @@
<template>
<a :href="params.data.urls.show" :title="params.data.name">
{{ params.data.name }}
</a>
</template>
<script>
export default {
props: {
params: {
type: Object,
required: true
}
}
};
</script>

View file

@ -30,28 +30,34 @@ import axios from '../../packs/custom_axios.js';
import DataTable from '../shared/datatable/table.vue';
import DeleteModal from '../shared/confirmation_modal.vue';
import NameRenderer from './renderers/name.vue';
import DefaultRenderer from './renderers/default.vue';
import FormatRenderer from './renderers/format.vue';
export default {
name: 'LabelTemplatesTable',
components: {
DataTable,
DeleteModal,
NameRenderer,
DefaultRenderer,
FormatRenderer
},
props: {
dataSource: {
type: String,
required: true,
required: true
},
actionsUrl: {
type: String,
required: true,
required: true
},
createUrl: {
type: String,
type: String
},
syncFluicsUrl: {
type: String,
},
type: String
}
},
data() {
return {
@ -60,40 +66,40 @@ export default {
{
field: 'default',
headerName: this.i18n.t('label_templates.index.default_label'),
cellRenderer: this.defaultRenderer,
sortable: true,
cellRenderer: 'DefaultRenderer',
sortable: true
}, {
field: 'name',
headerName: this.i18n.t('label_templates.index.thead_name'),
cellRenderer: this.labelNameRenderer,
sortable: true,
cellRenderer: 'NameRenderer',
sortable: true
}, {
field: 'format',
headerName: this.i18n.t('label_templates.index.format'),
sortable: true,
cellRenderer: ({ data: { format, icon_url: iconUrl } }) => `<span>${iconUrl}</span> <span>${format}</span>`
cellRenderer: 'FormatRenderer'
}, {
field: 'description',
headerName: this.i18n.t('label_templates.index.description'),
sortable: true,
sortable: true
}, {
field: 'modified_by',
headerName: this.i18n.t('label_templates.index.updated_by'),
sortable: true,
sortable: true
}, {
field: 'updated_at',
headerName: this.i18n.t('label_templates.index.updated_at'),
sortable: true,
sortable: true
}, {
field: 'created_by',
headerName: this.i18n.t('label_templates.index.created_by'),
sortable: true,
sortable: true
}, {
field: 'created_at',
headerName: this.i18n.t('label_templates.index.created_at'),
sortable: true,
},
],
sortable: true
}
]
};
},
computed: {
@ -106,7 +112,7 @@ export default {
label: this.i18n.t('label_templates.index.toolbar.new'),
type: 'emit',
path: this.createUrl,
buttonStyle: 'btn btn-primary',
buttonStyle: 'btn btn-primary'
});
}
if (this.syncFluicsUrl) {
@ -121,21 +127,11 @@ export default {
}
return {
left,
right: [],
right: []
};
},
}
},
methods: {
labelNameRenderer(params) {
const editUrl = params.data.urls.show;
return `<a href="${editUrl}" title="${params.data.name}">
${params.data.name}
</a>`;
},
defaultRenderer(params) {
const defaultSelected = params.data.default;
return defaultSelected ? '<i class="sn-icon sn-icon-approval"></i>' : '';
},
setDefault(action) {
axios.post(action.path).then((response) => {
this.reloadingTable = true;
@ -175,8 +171,8 @@ export default {
HelperModule.flashAlertMsg(error.response.data.error, 'danger');
});
}
},
},
}
}
};
</script>

View file

@ -16,6 +16,7 @@
:hiddenDataMessage="i18n.t('experiments.empty_state.no_active_modules_archived_branch')"
scrollMode="infinite"
@tableReloaded="reloadingTable = false"
@reloadTable="reloadingTable = true"
@create="newModalOpen = true"
@edit="edit"
@move="move"
@ -56,6 +57,9 @@
import axios from '../../packs/custom_axios.js';
import DataTable from '../shared/datatable/table.vue';
import ConfirmationModal from '../shared/confirmation_modal.vue';
import NameRenderer from './renderers/name.vue';
import ResultsRenderer from './renderers/results.vue';
import StatusRenderer from './renderers/status.vue';
import DueDateRenderer from './renderers/due_date.vue';
import DesignatedUsers from './renderers/designated_users.vue';
import TagsModal from './modals/tags.vue';
@ -77,7 +81,10 @@ export default {
NewModal,
EditModal,
MoveModal,
AccessModal
AccessModal,
NameRenderer,
ResultsRenderer,
StatusRenderer
},
props: {
dataSource: { type: String, required: true },
@ -115,7 +122,7 @@ export default {
field: 'name',
headerName: this.i18n.t('experiments.table.column.task_name_html'),
sortable: true,
cellRenderer: this.nameRenderer
cellRenderer: NameRenderer
},
{
field: 'code',
@ -133,7 +140,7 @@ export default {
field: 'results',
headerName: this.i18n.t('experiments.table.column.results_html'),
sortable: true,
cellRenderer: this.resultsRenderer
cellRenderer: ResultsRenderer
},
{
field: 'age',
@ -144,7 +151,7 @@ export default {
field: 'status',
headerName: this.i18n.t('experiments.table.column.status_html'),
sortable: true,
cellRenderer: this.statusRenderer,
cellRenderer: StatusRenderer,
minWidth: 120
}
];
@ -321,53 +328,6 @@ export default {
roles_path: this.userRolesUrl
};
},
checkProvisioning(params) {
if (params.data.provisioning_status === 'done') return;
axios.get(params.data.urls.provisioning_status).then((response) => {
const provisioningStatus = response.data.provisioning_status;
if (provisioningStatus === 'done') {
this.reloadingTable = true;
} else {
setTimeout(() => {
this.checkProvisioning(params);
}, 5000);
}
});
},
// Renderers
nameRenderer(params) {
const { name, urls } = params.data;
const provisioningStatus = params.data.provisioning_status;
if (provisioningStatus === 'in_progress') {
setTimeout(() => {
this.checkProvisioning(params);
}, 5000);
return `
<span class="flex gap-2 items-center">
<div title="${this.i18n.t('experiments.duplicate_tasks.duplicating')}"
class="loading-overlay w-6 h-6 !relative shrink-0" data-toggle="tooltip" data-placement="right"></div>
<span class="truncate">${name}</span>
</span>`;
}
return `<a href="${urls.show}" title="${name}" ><span class="truncate">${name}</span></a>`;
},
statusRenderer(params) {
const { status } = params.data;
return `<span
class="px-2 py-1 border border-solid rounded truncate ${!status.light_color ? 'text-sn-white' : ''}"
style="background-color: ${status.color};"
>
${status.name}
</span>`;
},
resultsRenderer(params) {
const { results, urls } = params.data;
return `<a href="${urls.results}" >${results}</a>`;
},
usersFilterRenderer(option) {
return `<div class="flex items-center gap-2">
<img src="${option[2].avatar_url}" class="rounded-full w-6 h-6" />

View file

@ -0,0 +1,50 @@
<template>
<template v-if="params.data.provisioning_status === 'in_progress'">
<span class="flex gap-2 items-center">
<div :title="this.i18n.t('experiments.duplicate_tasks.duplicating')"
class="loading-overlay w-6 h-6 !relative shrink-0" data-toggle="tooltip" data-placement="right"></div>
<span class="truncate">{{ params.data.name }}</span>
</span>
</template>
<template v-else>
<a :href="params.data.urls.show" :title="params.data.name" >
<i v-if="params.data.locked" class="sn-icon sn-icon-locked-task"></i>
<span class="truncate">{{ params.data.name }}</span>
</a>
</template>
</template>
<script>
import axios from '../../../packs/custom_axios.js';
export default {
name: 'NameRenderer',
props: {
params: {
required: true
}
},
created() {
if (this.params.data.provisioning_status === 'in_progress') {
setTimeout(() => {
this.checkProvisioning();
}, 5000);
}
},
methods: {
checkProvisioning() {
if (this.params.data.provisioning_status === 'done') return;
axios.get(this.params.data.urls.provisioning_status).then((response) => {
const provisioningStatus = response.data.provisioning_status;
if (provisioningStatus === 'done') {
this.params.dtComponent.$emit('reloadTable', null, [this.params.data]);
} else {
setTimeout(() => {
this.checkProvisioning();
}, 5000);
}
});
}
}
};
</script>

View file

@ -0,0 +1,14 @@
<template>
<a :href="params.data.urls.results" >{{ params.data.results }}</a>
</template>
<script>
export default {
name: 'ResultsRenderer',
props: {
params: {
required: true
}
}
};
</script>

View file

@ -0,0 +1,20 @@
<template>
<span
class="px-2 py-1 border border-solid rounded truncate"
:class="{'text-sn-white' : !params.data.status.light_color}"
:style="{'background-color': params.data.status.color}"
>
{{ params.data.status.name }}
</span>
</template>
<script>
export default {
name: 'StatusRenderer',
props: {
params: {
required: true
}
}
};
</script>

View file

@ -68,6 +68,7 @@ import axios from '../../packs/custom_axios.js';
import DataTable from '../shared/datatable/table.vue';
import UsersRenderer from './renderers/users.vue';
import NameRenderer from './renderers/name.vue';
import CommentsRenderer from '../shared/datatable/renderers/comments.vue';
import ProjectCard from './card.vue';
import ConfirmationModal from '../shared/confirmation_modal.vue';
@ -84,6 +85,7 @@ export default {
components: {
DataTable,
UsersRenderer,
NameRenderer,
ProjectCard,
ConfirmationModal,
EditProjectModal,
@ -129,7 +131,7 @@ export default {
flex: 1,
headerName: this.i18n.t('projects.index.card.name'),
sortable: true,
cellRenderer: this.nameRenderer
cellRenderer: 'NameRenderer'
},
{
field: 'code',
@ -252,16 +254,6 @@ export default {
<span title="${option[1]}" class="truncate">${option[1]}</span>
</div>`;
},
nameRenderer(params) {
const showUrl = params.data.urls.show;
return `<a href="${showUrl}"
class="flex items-center gap-1 hover:no-underline
${!showUrl ? 'pointer-events-none text-sn-grey' : ''}"
title="${params.data.name}">
${params.data.folder ? '<i class="sn-icon mini sn-icon-mini-folder-left"></i>' : ''}
<span class="truncate">${params.data.name} </span>
</a>`;
},
openComments(_params, rows) {
$(this.$refs.commentButton).data('objectId', rows[0].id);
this.$refs.commentButton.click();

View file

@ -0,0 +1,21 @@
<template>
<a :href="params.data.urls.show"
class="flex items-center gap-1 hover:no-underline"
:class="{
'pointer-events-none text-sn-grey': !params.data.urls.show
}"
:title="params.data.name">
<i v-if="params.data.folder" class="sn-icon mini sn-icon-mini-folder-left"></i>
<span class="truncate">{{ params.data.name }}</span>
</a>
</template>
<script>
export default {
name: 'NameRenderer',
props: {
params: {
required: true
}
}
};
</script>

View file

@ -19,8 +19,8 @@ export default {
name: 'UsersRenderer',
props: {
params: {
required: true,
},
required: true
}
},
computed: {
users() {

View file

@ -0,0 +1,21 @@
<template>
<a v-if="params.data.urls.show"
:href="params.data.urls.show"
:title="params.data.name">
{{ params.data.name }}
</a>
<span v-else class="text-sn-grey" :title="params.data.name">
{{ params.data.name }}
</span>
</template>
<script>
export default {
props: {
params: {
type: Object,
required: true
}
}
};
</script>

View file

@ -42,6 +42,7 @@ import axios from '../../packs/custom_axios.js';
import DataTable from '../shared/datatable/table.vue';
import UsersRenderer from '../projects/renderers/users.vue';
import NameRenderer from './renderers/name.vue';
import NewProtocolModal from './modals/new.vue';
import AccessModal from '../shared/access_modal/modal.vue';
import KeywordsRenderer from './renderers/keywords.vue';
@ -55,6 +56,7 @@ export default {
components: {
DataTable,
UsersRenderer,
NameRenderer,
NewProtocolModal,
AccessModal,
KeywordsRenderer,
@ -116,7 +118,7 @@ export default {
headerName: this.i18n.t('protocols.index.thead.name'),
sortable: true,
notSelectable: true,
cellRenderer: this.nameRenderer
cellRenderer: 'NameRenderer'
},
{
field: 'code',
@ -337,14 +339,6 @@ export default {
linkedMyModules(_event, rows) {
[this.linkedMyModulesModalObject] = rows;
},
// renderers
nameRenderer(params) {
const { urls, name } = params.data;
if (urls.show) {
return `<a href="${urls.show}" title="${name}">${name}</a>`;
}
return `<span class="text-sn-grey" title="${name}">${name}</span>`;
},
usersFilterRenderer(option) {
return `<div class="flex items-center gap-2">
<img src="${option[2].avatar_url}" class="rounded-full w-6 h-6" />

View file

@ -0,0 +1,16 @@
<template>
<span :title="params.data.name">
{{ params.data.name }}
</span>
</template>
<script>
export default {
props: {
params: {
type: Object,
required: true
}
}
};
</script>

View file

@ -43,6 +43,7 @@ import axios from '../../packs/custom_axios.js';
import DataTable from '../shared/datatable/table.vue';
import DocxRenderer from './renderers/docx.vue';
import PdfRenderer from './renderers/pdf.vue';
import NameRenderer from './renderers/name.vue';
import ConfirmationModal from '../shared/confirmation_modal.vue';
import SaveToInventoryModal from './modals/save_to_inventory.vue';
import UpdateReportModal from './modals/update.vue';
@ -60,6 +61,7 @@ export default {
DataTable,
DocxRenderer,
PdfRenderer,
NameRenderer,
ConfirmationModal,
SaveToInventoryModal,
UpdateReportModal
@ -108,7 +110,7 @@ export default {
field: 'name',
headerName: this.i18n.t('projects.reports.index.thead_name'),
sortable: true,
cellRenderer: ({ data: { name } }) => `<span title="${name}">${name}</span>`
cellRenderer: 'NameRenderer'
}, {
field: 'code',
headerName: this.i18n.t('projects.reports.index.thead_id'),

View file

@ -0,0 +1,22 @@
<template>
<a class="hover:no-underline flex items-center gap-1"
:title="params.data.name"
:href="params.data.urls.show"
>
<span class="truncate">
<i v-if="params.data.shared || params.data.ishared" class="fas fa-users"></i>
{{ params.data.name }}
</span>
</a>
</template>
<script>
export default {
props: {
params: {
type: Object,
required: true
}
}
};
</script>

View file

@ -68,6 +68,7 @@ import EditRepositoryModal from './modals/edit.vue';
import DuplicateRepositoryModal from './modals/duplicate.vue';
import ShareRepositoryModal from './modals/share.vue';
import DataTable from '../shared/datatable/table.vue';
import NameRenderer from './renderers/name.vue';
export default {
name: 'RepositoriesTable',
@ -78,7 +79,8 @@ export default {
NewRepositoryModal,
EditRepositoryModal,
DuplicateRepositoryModal,
ShareRepositoryModal
ShareRepositoryModal,
NameRenderer
},
props: {
dataSource: {
@ -138,7 +140,7 @@ export default {
headerName: this.i18n.t('libraries.index.table.name'),
sortable: true,
notSelectable: true,
cellRenderer: this.nameRenderer
cellRenderer: 'NameRenderer'
},
{
field: 'code',
@ -277,23 +279,6 @@ export default {
share(_event, rows) {
const [repository] = rows;
this.shareRepository = repository;
},
// Renderers
nameRenderer(params) {
const {
name,
urls,
shared,
ishared
} = params.data;
let sharedIcon = '';
if (shared || ishared) {
sharedIcon = '<i class="fas fa-users"></i>';
}
return `<a class="hover:no-underline flex items-center gap-1"
title="${name}" href="${urls.show}">
<span class="truncate">${sharedIcon}${name}</span>
</a>`;
}
}
};

View file

@ -1,7 +1,10 @@
/* global GLOBAL_CONSTANTS */
import axios from '../../../../../packs/custom_axios.js';
import { satisfies } from 'compare-versions';
import editLaunchingApplicationModal from '../../modal/edit_launching_application_modal.vue';
import NoPredefinedAppModal from '../../modal/no_predefined_app_modal.vue';
import RestrictedExtensionModal from '../../modal/restricted_extension_modal.vue';
import UpdateVersionModal from '../../modal/update_version_modal.vue';
export default {
@ -10,15 +13,17 @@ export default {
localAppName: null,
scinoteEditRunning: null,
showNoPredefinedAppModal: false,
showRestrictedExtensionModal: false,
showUpdateVersionModal: false,
editAppModal: false,
pollingInterval: null,
pollingInterval: null
};
},
components: {
editLaunchingApplicationModal,
NoPredefinedAppModal,
UpdateVersionModal
UpdateVersionModal,
RestrictedExtensionModal
},
computed: {
attributes() {
@ -93,9 +98,16 @@ export default {
}
},
async openLocally() {
const restrictedExtension = GLOBAL_CONSTANTS.SCINOTE_EDIT_RESTRICTED_EXTENSIONS.includes(
this.attributes.file_extension.toUpperCase()
);
if (this.isWrongVersion(window.scinoteEditVersion)) {
this.showUpdateVersionModal = true;
return;
} else if (restrictedExtension) {
this.showRestrictedExtensionModal = true;
return;
} else if (this.localAppName === null) {
this.showNoPredefinedAppModal = true;
return;

View file

@ -33,6 +33,10 @@
:fileName="attachment.attributes.file_name"
@close="showNoPredefinedAppModal = false"
/>
<RestrictedExtensionModal
v-if="showRestrictedExtensionModal"
@close="showRestrictedExtensionModal = false"
/>
<editLaunchingApplicationModal
v-if="editAppModal"
:fileName="attachment.attributes.file_name"

View file

@ -93,6 +93,10 @@
:fileName="attachment.attributes.file_name"
@close="showNoPredefinedAppModal = false"
/>
<RestrictedExtensionModal
v-if="showRestrictedExtensionModal"
@close="showRestrictedExtensionModal = false"
/>
<UpdateVersionModal
v-if="showUpdateVersionModal"
@close="showUpdateVersionModal = false"

View file

@ -0,0 +1,29 @@
<template>
<div ref="modal" @keydown.esc="cancel" class="modal" id="RestrictedExtensionModal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-md" 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" id="modal-delete-result-element">
{{ i18n.t('assets.restricted_extension_modal.title') }}
</h4>
</div>
<div class="modal-body">
<p v-html="i18n.t('assets.restricted_extension_modal.body')"></p>
</div>
<div class="modal-footer">
<button class="btn btn-primary" @click="close">{{ this.i18n.t('assets.restricted_extension_modal.action') }}</button>
</div>
</div>
</div>
</div>
</template>
<script>
import modalMixin from '../../modal_mixin';
export default {
name: 'RestrictedExtensionModal',
mixins: [modalMixin]
};
</script>

View file

@ -447,6 +447,12 @@ class Constants
MIN_SCINOTE_EDIT_VERSION = ENV['MIN_SCINOTE_EDIT_VERSION'].freeze
MAX_SCINOTE_EDIT_VERSION = ENV['MAX_SCINOTE_EDIT_VERSION'].freeze
# SciNote Edit unsupported extensions
SCINOTE_EDIT_RESTRICTED_EXTENSIONS = %w(
ACTION APP BIN COMMAND CSH OSX WORKFLOW DMG BAT BIN CAB CMD COM CPL EX_ EXE GADGET INF1 INS INX ISU JOB
JSE LNK MSC MSI MSP MST PAF PIF PS1 REG RGS SCR SCT SHB SHS U3P VB VBE VBS VBSCRIPT WS WSF WSH
).freeze
# quick search
QUICK_SEARCH_LIMIT = 5
QUICK_SEARCH_SEARCHABLE_OBJECTS = %w(project experiment my_module protocol repository_row

View file

@ -3907,6 +3907,10 @@ en:
body_text_html: "The specified application for accessing the <b>%{file_name}</b> is not preconfigured. To successfully open this file, please set up the appropriate application in your local environment beforehand.</p>"
understand_button: "I understand"
set_up_app: "Set up an application to open this file"
restricted_extension_modal:
title: "Open file - Security warning"
body: "This file type appears to be executable and can potentially harm your computer. It cannot be opened with SciNote Edit. To open the file please download it manually."
action: "I understand"
update_version_modal:
title: "Update required"
body_text_html: "The current version of the SciNote Edit application is no longer supported. To ensure a seamless and secure user experience, we recommend updating to the latest version."