mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-09-30 17:04:31 +08:00
Merge branch 'develop' into features/user-groups
This commit is contained in:
commit
a6e5cc9d2e
16 changed files with 150 additions and 81 deletions
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
1.42.0
|
||||
1.42.0.1
|
||||
|
|
|
@ -255,7 +255,6 @@ label {
|
|||
|
||||
.user-time {
|
||||
color: $color-silver-chalice;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.report-element-body {
|
||||
|
@ -462,7 +461,6 @@ label {
|
|||
|
||||
.user-time {
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -41,7 +41,12 @@ class ResultsController < ApplicationController
|
|||
end
|
||||
|
||||
def list
|
||||
if params[:with_linked_step_id].present?
|
||||
step = @my_module.protocol.steps.find_by(id: params[:with_linked_step_id])
|
||||
@results = @my_module.results.where(archived: false).or(@my_module.results.where(id: step.results.select(:id)))
|
||||
else
|
||||
@results = @my_module.results.active
|
||||
end
|
||||
|
||||
update_and_apply_user_sort_preference!
|
||||
end
|
||||
|
|
|
@ -87,7 +87,8 @@ class StepResultsController < ApplicationController
|
|||
my_module: my_module.id,
|
||||
step: step.id,
|
||||
result: result.id,
|
||||
position: step.position + 1
|
||||
step_position: { id: step.id,
|
||||
value_for: 'position_plus_one' }
|
||||
})
|
||||
end
|
||||
end
|
||||
|
|
|
@ -285,7 +285,7 @@ class StepsController < ApplicationController
|
|||
render json: {
|
||||
paginated: true,
|
||||
next_page: steps.next_page,
|
||||
data: steps.map { |step| [step.id, step.name] }
|
||||
data: steps.map { |step| [step.id, step.name, { position: step.position }] }
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
@ -11,7 +11,10 @@
|
|||
{{ i18n.t('protocols.steps.modals.link_results.title') }}
|
||||
</h4>
|
||||
</div>
|
||||
<div v-if="results.length > 0" class="modal-body">
|
||||
<div v-if="loading" class="modal-body h-40 flex items-center justify-center">
|
||||
<div class="sci-loader"></div>
|
||||
</div>
|
||||
<div v-else-if="results.length > 0" class="modal-body">
|
||||
<p>
|
||||
{{ i18n.t('protocols.steps.modals.link_results.description') }}
|
||||
</p>
|
||||
|
@ -32,7 +35,7 @@
|
|||
{{ i18n.t('protocols.steps.modals.link_results.empty_description') }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div v-if="!loading" class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">
|
||||
{{ i18n.t('general.cancel') }}
|
||||
</button>
|
||||
|
@ -89,12 +92,13 @@ export default {
|
|||
return {
|
||||
results: [],
|
||||
initialResults: [],
|
||||
selectedResults: []
|
||||
selectedResults: [],
|
||||
loading: true
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
resultsListUrl() {
|
||||
return list_my_module_results_path({ my_module_id: this.step.attributes.my_module_id });
|
||||
return list_my_module_results_path({ my_module_id: this.step.attributes.my_module_id, with_linked_step_id: this.step.id });
|
||||
},
|
||||
resultsPageUrl() {
|
||||
return my_module_results_path({ my_module_id: this.step.attributes.my_module_id });
|
||||
|
@ -132,6 +136,10 @@ export default {
|
|||
axios.get(this.resultsListUrl)
|
||||
.then((response) => {
|
||||
this.results = response.data;
|
||||
this.loading = false;
|
||||
}).catch(() => {
|
||||
HelperModule.flashAlertMsg(I18n.t('general.error'), 'danger');
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -83,14 +83,15 @@
|
|||
:data-object-type="step.attributes.type"
|
||||
tabindex="0"
|
||||
></span> <!-- Hidden element to support legacy code -->
|
||||
<template v-if="!inRepository">
|
||||
<template v-if="step.attributes.results.length == 0">
|
||||
<button v-if="urls.update_url" :title="i18n.t('protocols.steps.link_results')" class="btn btn-light icon-btn" @click="this.openLinkResultsModal = true">
|
||||
<button ref="linkButton" v-if="urls.update_url" :title="i18n.t('protocols.steps.link_results')" class="btn btn-light icon-btn" @click="this.openLinkResultsModal = true">
|
||||
<i class="sn-icon sn-icon-results"></i>
|
||||
</button>
|
||||
</template>
|
||||
<GeneralDropdown v-else ref="linkedResultsDropdown" position="right">
|
||||
<template v-slot:field>
|
||||
<button class="btn btn-light icon-btn" :title="i18n.t('protocols.steps.linked_results')">
|
||||
<button ref="linkButton" class="btn btn-light icon-btn" :title="i18n.t('protocols.steps.linked_results')">
|
||||
<i class="sn-icon sn-icon-results"></i>
|
||||
<span class="absolute top-1 right-1 h-4 min-w-4 bg-sn-science-blue text-white flex items-center justify-center rounded-full text-[10px]">
|
||||
{{ step.attributes.results.length }}
|
||||
|
@ -117,6 +118,7 @@
|
|||
</template>
|
||||
</template>
|
||||
</GeneralDropdown>
|
||||
</template>
|
||||
<a href=" #"
|
||||
v-if="!inRepository"
|
||||
ref="comments"
|
||||
|
@ -214,12 +216,14 @@
|
|||
@close="openFormSelectModal = false"
|
||||
@submit="createElement('form_response', null, null, $event); openFormSelectModal = false"
|
||||
/>
|
||||
<Teleport to="body">
|
||||
<LinkResultsModal
|
||||
v-if="openLinkResultsModal"
|
||||
:step="step"
|
||||
@updateStep="updateLinkedResults"
|
||||
@close="openLinkResultsModal = false"
|
||||
/>
|
||||
</Teleport>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -399,6 +403,11 @@
|
|||
$(this.$refs.elementsDropdownButton).on('shown.bs.dropdown hidden.bs.dropdown', () => {
|
||||
this.handleDropdownPosition(this.$refs.elementsDropdownButton, this.$refs.elementsDropdown)
|
||||
});
|
||||
|
||||
window.initTooltip(this.$refs.linkButton);
|
||||
},
|
||||
beforeUnmount() {
|
||||
window.destroyTooltip(this.$refs.linkButton);
|
||||
},
|
||||
computed: {
|
||||
reorderableElements() {
|
||||
|
@ -801,10 +810,14 @@
|
|||
});
|
||||
},
|
||||
updateLinkedResults(results) {
|
||||
window.destroyTooltip(this.$refs.linkButton);
|
||||
|
||||
this.$emit('step:update', {
|
||||
results: results,
|
||||
position: this.step.attributes.position
|
||||
})
|
||||
});
|
||||
|
||||
this.$nextTick(() => window.initTooltip(this.$refs.linkButton));
|
||||
},
|
||||
resultUrl(result_id, archived) {
|
||||
return my_module_results_path({my_module_id: this.step.attributes.my_module_id, result_id: result_id, view_mode: (archived ? 'archived' : 'active') });
|
||||
|
|
|
@ -11,7 +11,10 @@
|
|||
{{ i18n.t('my_modules.results.modals.link_steps.title') }}
|
||||
</h4>
|
||||
</div>
|
||||
<div v-if="steps.length > 0" class="modal-body">
|
||||
<div v-if="loading" class="modal-body h-40 flex items-center justify-center">
|
||||
<div class="sci-loader"></div>
|
||||
</div>
|
||||
<div v-else-if="steps.length > 0" class="modal-body">
|
||||
<p>
|
||||
{{ i18n.t('my_modules.results.modals.link_steps.description') }}
|
||||
</p>
|
||||
|
@ -22,6 +25,7 @@
|
|||
:value="selectedSteps"
|
||||
:searchable="true"
|
||||
@change="changeSteps"
|
||||
:option-renderer="stepRenderer"
|
||||
:multiple="true"
|
||||
:withCheckboxes="true"
|
||||
:placeholder="i18n.t('my_modules.results.modals.link_steps.placeholder')" />
|
||||
|
@ -32,7 +36,7 @@
|
|||
{{ i18n.t('my_modules.results.modals.link_steps.empty_description') }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div v-if="!loading" class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">
|
||||
{{ i18n.t('general.cancel') }}
|
||||
</button>
|
||||
|
@ -93,7 +97,8 @@ export default {
|
|||
return {
|
||||
steps: [],
|
||||
selectedSteps: [],
|
||||
initialSteps: []
|
||||
initialSteps: [],
|
||||
loading: true
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
@ -135,8 +140,15 @@ export default {
|
|||
loadSteps() {
|
||||
axios.get(this.stepsListUrl)
|
||||
.then((response) => {
|
||||
this.loading = false;
|
||||
this.steps = response.data;
|
||||
}).catch(() => {
|
||||
HelperModule.flashAlertMsg(I18n.t('general.error'), 'danger');
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
stepRenderer(option) {
|
||||
return `${option[2].position + 1}. ${option[1]}`;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -66,13 +66,13 @@
|
|||
tabindex="0"
|
||||
></span> <!-- Hidden element to support legacy code -->
|
||||
<tempplate v-if="result.attributes.steps.length == 0">
|
||||
<button v-if="urls.update_url" :title="i18n.t('my_modules.results.link_steps')" class="btn btn-light icon-btn" @click="this.openLinkStepsModal = true">
|
||||
<button v-if="urls.update_url" ref="linkButton" :title="i18n.t('my_modules.results.link_steps')" class="btn btn-light icon-btn" @click="this.openLinkStepsModal = true">
|
||||
{{ i18n.t('my_modules.results.link_to_step') }}
|
||||
</button>
|
||||
</tempplate>
|
||||
<GeneralDropdown v-else ref="linkedStepsDropdown" position="right">
|
||||
<template v-slot:field>
|
||||
<button class="btn btn-light icon-btn" :title="i18n.t('my_modules.results.linked_steps')">
|
||||
<button ref="linkButton" class="btn btn-light icon-btn" :title="i18n.t('my_modules.results.linked_steps')">
|
||||
<i class="sn-icon sn-icon-steps"></i>
|
||||
<span class="absolute top-1 -right-1 h-4 min-w-4 bg-sn-science-blue text-white flex items-center justify-center rounded-full text-[10px]">
|
||||
{{ result.attributes.steps.length }}
|
||||
|
@ -181,6 +181,7 @@
|
|||
@cancel="closeCustomWellPlateModal"
|
||||
@create:table="(...args) => this.createElement('table', ...args)"
|
||||
/>
|
||||
<Teleport to="body">
|
||||
<LinkStepsModal
|
||||
v-if="openLinkStepsModal"
|
||||
:result="result"
|
||||
|
@ -188,6 +189,7 @@
|
|||
@updateResult="updateLinkedSteps"
|
||||
@close="openLinkStepsModal = false"
|
||||
/>
|
||||
</Teleport>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -302,6 +304,11 @@ export default {
|
|||
}
|
||||
this.$emit('result:collapsed');
|
||||
});
|
||||
|
||||
window.initTooltip(this.$refs.linkButton);
|
||||
},
|
||||
beforeUnmount() {
|
||||
window.destroyTooltip(this.$refs.linkButton);
|
||||
},
|
||||
computed: {
|
||||
reorderableElements() {
|
||||
|
@ -627,9 +634,13 @@ export default {
|
|||
});
|
||||
},
|
||||
updateLinkedSteps(steps) {
|
||||
window.destroyTooltip(this.$refs.linkButton);
|
||||
|
||||
this.$emit('result:update', this.result.id,{
|
||||
steps: steps
|
||||
})
|
||||
});
|
||||
|
||||
this.$nextTick(() => window.initTooltip(this.$refs.linkButton));
|
||||
},
|
||||
protocolUrl(step_id) {
|
||||
return protocols_my_module_path({ id: this.result.attributes.my_module_id }, { step_id: step_id })
|
||||
|
|
|
@ -270,7 +270,8 @@ export default {
|
|||
maxWidth: 40,
|
||||
resizable: true,
|
||||
pinned: 'left',
|
||||
lockPosition: 'left'
|
||||
lockPosition: 'left',
|
||||
sortable: false
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -453,6 +454,9 @@ export default {
|
|||
currentViewRender: this.currentViewRender,
|
||||
perPage: this.perPage
|
||||
};
|
||||
|
||||
columnsState.find((column) => column.colId === 'checkbox').pinned = 'left';
|
||||
|
||||
const settings = {
|
||||
key: this.stateKey,
|
||||
data: tableState
|
||||
|
@ -532,6 +536,11 @@ export default {
|
|||
this.restoreSelection();
|
||||
|
||||
this.handleScroll();
|
||||
})
|
||||
.catch(() => {
|
||||
this.dataLoading = false;
|
||||
this.$emit('tableReloaded', [], { filtered: this.searchValue.length > 0 });
|
||||
window.HelperModule.flashAlertMsg(this.i18n.t('general.error'), 'danger');
|
||||
});
|
||||
},
|
||||
handleInfiniteScroll(response) {
|
||||
|
|
|
@ -260,7 +260,7 @@ class Protocol < ApplicationRecord
|
|||
|
||||
def self.viewable_by_user_my_module_protocols(user, teams)
|
||||
distinct.joins(:my_module)
|
||||
.where(my_modules: MyModule.viewable_by_user(user, teams))
|
||||
.where(my_modules: { id: MyModule.viewable_by_user(user, teams).select(:id) })
|
||||
end
|
||||
|
||||
def self.filter_by_teams(teams = [])
|
||||
|
|
|
@ -39,6 +39,7 @@ class Result < ApplicationRecord
|
|||
accepts_nested_attributes_for :tables
|
||||
|
||||
before_save :ensure_default_name
|
||||
after_discard :delete_step_results
|
||||
after_discard do
|
||||
CleanupUserSettingsJob.perform_later('result_states', id)
|
||||
end
|
||||
|
@ -51,8 +52,12 @@ class Result < ApplicationRecord
|
|||
teams = options[:teams] || current_team || user.teams.select(:id)
|
||||
|
||||
new_query = joins(:my_module)
|
||||
.where(my_modules: MyModule.with_granted_permissions(user, MyModulePermissions::READ)
|
||||
.where(user_assignments: { team: teams }))
|
||||
.where(
|
||||
my_modules: {
|
||||
id: MyModule.with_granted_permissions(user, MyModulePermissions::READ)
|
||||
.where(user_assignments: { team: teams }).select(:id)
|
||||
}
|
||||
)
|
||||
|
||||
unless include_archived
|
||||
new_query = new_query.joins(my_module: { experiment: :project })
|
||||
|
@ -187,4 +192,8 @@ class Result < ApplicationRecord
|
|||
def ensure_default_name
|
||||
self.name = name.presence || I18n.t('my_modules.results.default_name')
|
||||
end
|
||||
|
||||
def delete_step_results
|
||||
step_results.destroy_all
|
||||
end
|
||||
end
|
||||
|
|
|
@ -81,6 +81,9 @@ module Scinote
|
|||
|
||||
config.x.export_all_limit_24h = (ENV['EXPORT_ALL_LIMIT_24_HOURS'] || 3).to_i
|
||||
|
||||
# Fallback to old variant behaviour (pre 7.2)
|
||||
config.active_storage.track_variants = false
|
||||
|
||||
# SciNote Core Application version
|
||||
VERSION = File.read(Rails.root.join('VERSION')).strip.freeze
|
||||
|
||||
|
|
|
@ -430,7 +430,7 @@ en:
|
|||
|
||||
attachments:
|
||||
menu:
|
||||
office_file: "New Office file"
|
||||
office_file: "New Microsoft 365 file"
|
||||
chemical_drawing: "New chemical drawing"
|
||||
file_from_pc: "File from your PC"
|
||||
modified_label: "Modified:"
|
||||
|
@ -2215,9 +2215,9 @@ en:
|
|||
success_flash: "File result successfully deleted."
|
||||
wopi_open_file: "Open in %{app}"
|
||||
wopi_edit_file: "Open in %{app}"
|
||||
wopi_word: "Microsoft Word for the Web"
|
||||
wopi_excel: "Microsoft Excel for the Web"
|
||||
wopi_powerpoint: "Microsoft PowerPoint for the Web"
|
||||
wopi_word: "Microsoft Word for the web"
|
||||
wopi_excel: "Microsoft Excel for the web"
|
||||
wopi_powerpoint: "Microsoft PowerPoint for the web"
|
||||
error_flash: 'Something went wrong! Please try again later.'
|
||||
|
||||
result_tables:
|
||||
|
@ -4440,9 +4440,9 @@ en:
|
|||
wopi_supported_table_formats_title: 'Only .xlsx, .xlsm, .xlsb, .ods file formats are supported for editing in Excel for the web.'
|
||||
wopi_supported_presentation_formats_title: 'Only .pptx, ppsx, .odp file formats are supported for editing in PowerPoint for the web.'
|
||||
create_wopi_file:
|
||||
button_text: 'New Microsoft Office file'
|
||||
li_text: "Office file"
|
||||
modal_title: 'Create new Microsoft Office document'
|
||||
button_text: 'New Microsoft 365 file'
|
||||
li_text: "Microsoft 365 file"
|
||||
modal_title: 'Create new Microsoft 365 document'
|
||||
text_field_label: 'Document name'
|
||||
type_select_label: 'Document type'
|
||||
text_field_placeholder: 'Enter document name...'
|
||||
|
|
|
@ -122,12 +122,12 @@ en:
|
|||
move_experiment_html: "%{user} moved experiment %{experiment} from project %{project_original} to project %{project_new}."
|
||||
clone_experiment_html: "%{user} duplicated experiment %{experiment_new} from experiment %{experiment_original} as template."
|
||||
archive_experiment_html: "%{user} archived experiment %{experiment}."
|
||||
edit_wopi_file_on_result_html: "%{user} edited Office online file %{asset_name} on result %{result}: %{action}."
|
||||
edit_wopi_file_on_result_html: "%{user} edited Microsoft 365 file %{asset_name} on result %{result}: %{action}."
|
||||
export_protocol_from_task_html: "%{user} exported protocol from task %{my_module}"
|
||||
copy_protocol_in_repository_html: "%{user} duplicated protocol %{protocol_new} from protocol %{protocol_original} as a template"
|
||||
user_leave_team_html: "%{user} left team %{team}"
|
||||
edit_wopi_file_on_step_html: "%{user} edited Office online file %{asset_name} on protocol's step %{step_position} %{step} on task %{my_module}: %{action}."
|
||||
edit_wopi_file_on_step_in_repository_html: "%{user} edited Office online file %{asset_name} on protocol %{protocol}'s step %{step_position} %{step} in Protocol repository: %{action}."
|
||||
edit_wopi_file_on_step_html: "%{user} edited Microsoft 365 file %{asset_name} on protocol's step %{step_position} %{step} on task %{my_module}: %{action}."
|
||||
edit_wopi_file_on_step_in_repository_html: "%{user} edited Microsoft 365 file %{asset_name} on protocol %{protocol}'s step %{step_position} %{step} in Protocol repository: %{action}."
|
||||
restore_experiment_html: "%{user} restored experiment %{experiment}."
|
||||
rename_task_html: "%{user} renamed task %{my_module}."
|
||||
move_task_html: "%{user} moved task %{my_module} from experiment %{experiment_original} to experiment %{experiment_new}."
|
||||
|
@ -313,7 +313,7 @@ en:
|
|||
edit_task_result_file_locally_html: "%{user} locally edited file %{file} on result %{result}"
|
||||
export_inventories_html: "%{user} exported inventory %{inventories}"
|
||||
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}."
|
||||
edit_wopi_file_on_inventory_item_html: "%{user} edited Microsoft 365 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_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>."
|
||||
|
@ -410,6 +410,8 @@ en:
|
|||
my_module_access_changed_user_group_html: "%{user} changed %{user_group}'s role on task %{my_module} to %{role}."
|
||||
step_and_result_linked_html: "%{user} linked result <strong>%{result}</strong> and step <strong>%{position}</strong> <strong>%{step}</strong> on task <strong>%{my_module}</strong>."
|
||||
step_and_result_unlinked_html: "%{user} unlinked result <strong>%{result}</strong> and step <strong>%{position}</strong> <strong>%{step}</strong> on task <strong>%{my_module}</strong>."
|
||||
step_and_result_linked_html: "%{user} linked result <strong>%{result}</strong> and step <strong>%{step_position}</strong> <strong>%{step}</strong> on task <strong>%{my_module}</strong>."
|
||||
step_and_result_unlinked_html: "%{user} unlinked result <strong>%{result}</strong> and step <strong>%{step_position}</strong> <strong>%{step}</strong> on task <strong>%{my_module}</strong>."
|
||||
activity_name:
|
||||
create_project: "Project created"
|
||||
edit_project: "Project edited"
|
||||
|
@ -486,9 +488,9 @@ en:
|
|||
move_experiment: "Experiment moved"
|
||||
clone_experiment: "Experiment duplicated"
|
||||
archive_experiment: "Experiment archived"
|
||||
edit_wopi_file_on_result: "Office online file on result edited"
|
||||
edit_wopi_file_on_step: "Office online file on task step edited"
|
||||
edit_wopi_file_on_step_in_repository: "Office online file on step edited"
|
||||
edit_wopi_file_on_result: "Microsoft 365 file on result edited"
|
||||
edit_wopi_file_on_step: "Microsoft 365 file on task step edited"
|
||||
edit_wopi_file_on_step_in_repository: "Microsoft 365 file on step edited"
|
||||
restore_experiment: "Experiment restored"
|
||||
rename_task: "Task renamed"
|
||||
move_task: "Task moved"
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
include UsersGenerator
|
||||
|
||||
if ActiveRecord::Base.connection.migration_context.needs_migration?
|
||||
raise "There are pending migrations. Run 'rails db:migrate' first."
|
||||
end
|
||||
ActiveRecord::Migration.check_all_pending!
|
||||
|
||||
MyModuleStatusFlow.ensure_default
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue