diff --git a/run_dir/design/index.html b/run_dir/design/index.html index 001dba8ad..680852d4c 100644 --- a/run_dir/design/index.html +++ b/run_dir/design/index.html @@ -54,30 +54,16 @@
-
-

Longest waiting projects

-
- - - - - - - - - - -
Project:Library:Status:Days waiting:
-
+
+
-
-

Server Status

+
+

Server Status

- @@ -85,7 +71,6 @@

Server Status

{% for server, status in server_status %} - ` + - `` + - ``; - $("#prio_projs_table_body").append(tblRow); - }); - init_listjs(); - }); -}; - -const init_listjs = () => { - const table = $('#prio_projs_table').DataTable({ - paging: false, - destroy: true, - info: false, - order: [[3, "desc"]], - searching: false, - }); - //Add the bootstrap classes to the search thingy - $('div.dataTables_filter input').addClass('form-control search search-query'); - $('#prio_projs_table_filter').addClass('form-inline float-right'); - $("#prio_projs_table_filter").appendTo("h1"); - $('#prio_projs_table_filter label input').appendTo($('#prio_projs_table_filter')); - $('#prio_projs_table_filter label').remove(); - $("#prio_projs_table_filter input").attr("placeholder", "Search.."); - // Apply the search - table.columns().every(function() { - const that = this; - $('input', this.footer()).on('keyup change', function() { - that.search(this.value).draw(); - }); - }); -}; - $('body').on('click', '.group', (event) => { $($("#prio_projs_table").DataTable().column(0).header()).trigger("click"); }); @@ -135,5 +48,4 @@ $('body').on('click', '.group', (event) => { $(document).ready(() => { fill_last_updated_text(); fill_sensorpush_status_field(); - fill_prioprojs_table(); }); diff --git a/run_dir/static/js/index_vue.js b/run_dir/static/js/index_vue.js new file mode 100644 index 000000000..bddac4f9e --- /dev/null +++ b/run_dir/static/js/index_vue.js @@ -0,0 +1,8 @@ +import { vRunningNotesList, vRunningNoteSingle } from './running_notes_component.js' + +const vIndex = {} + +const app = Vue.createApp(vIndex) +app.component('v-running-notes-list', vRunningNotesList) +app.component('v-running-note-single', vRunningNoteSingle) +app.mount('#vue_index_main') diff --git a/run_dir/static/js/projects_main_vue.js b/run_dir/static/js/projects_main_vue.js index eb9288dd1..509007b82 100644 --- a/run_dir/static/js/projects_main_vue.js +++ b/run_dir/static/js/projects_main_vue.js @@ -1,6 +1,6 @@ import {vProjectCards, vProjectDataField, vProjectDetails} from './projects_components.js' import { getDropdownPosition } from './smart_suggestion.js'; -import { vRunningNotesTab, vRunningNoteSingle } from './running_notes_component.js' +import { vRunningNotesTab, vRunningNotesList, vRunningNoteSingle } from './running_notes_component.js' const vProjectsStatus = { @@ -440,5 +440,6 @@ app.component('v-project-data-field-tooltip', vProjectDataField) app.component('v-projects-cards', vProjectCards) app.component('v-project-details', vProjectDetails) app.component('v-running-note-single', vRunningNoteSingle) +app.component('v-running-notes-list', vRunningNotesList) app.component('v-running-notes-tab', vRunningNotesTab) app.mount('#v_projects_main') \ No newline at end of file diff --git a/run_dir/static/js/running_notes_component.js b/run_dir/static/js/running_notes_component.js index 0e3e65eb3..7618ad6be 100644 --- a/run_dir/static/js/running_notes_component.js +++ b/run_dir/static/js/running_notes_component.js @@ -20,13 +20,11 @@ export const vRunningNotesTab = { props: ['user', 'partition_id', 'all_users', 'note_type'], data() { return { - category_filter: 'All', dropdown_position: {}, form_categories: [], form_note_text: '', user_suggestions: [], running_notes: [], - search_term: '', submitting: false, show_help: false, cat_classes: cat_classes @@ -57,26 +55,6 @@ export const vRunningNotesTab = { height: this.dropdown_position.height + 'px' } }, - visible_running_notes() { - let running_notes_tmp = Object.entries(this.running_notes) - - if (this.search_term !== '') { - running_notes_tmp = running_notes_tmp.filter(([running_note_key, running_note]) => { - return (running_note.note.toLowerCase().includes(this.search_term.toLowerCase())) || - (running_note.user.toLowerCase().includes(this.search_term.toLowerCase())) || - (running_note.categories.join(' ').toLowerCase().includes(this.search_term.toLowerCase())) - }) - } - - // Filter by category - if (this.category_filter !== 'All') { - running_notes_tmp = running_notes_tmp.filter(([running_note_key, running_note]) => { - return running_note.categories.includes(this.category_filter) - }) - } - - return Object.fromEntries(running_notes_tmp) - }, visible_user_suggestions() { // Only show the first 5 suggestions return this.user_suggestions.slice(0, 5) @@ -92,19 +70,6 @@ export const vRunningNotesTab = { } return null }, - fetchAllRunningNotes(partition_id) { - axios - .get('/api/v1/running_notes/' + partition_id) - .then(response => { - let data = response.data - if (data !== null) { - this.running_notes = data; - } - }) - .catch(error => { - this.$root.error_messages.push('Unable to fetch running notes, please try again or contact a system administrator.') - }) - }, openNewNoteForm() { let new_note_form = this.$refs.new_note_form; let new_note_caret = this.$refs.new_note_caret; @@ -124,9 +89,6 @@ export const vRunningNotesTab = { } this.dropdown_position = this.$root.getDropdownPositionHelper(textarea, 100); }, - setFilter(filter) { - this.category_filter = filter - }, submitRunningNote() { this.submitting = true; if (this.form_note_text === '') { @@ -160,6 +122,7 @@ export const vRunningNotesTab = { axios .post('/api/v1/running_notes/' + this.partition_id, post_body) .then(response => { + alert("TODO: fetch new running notes automatically") this.fetchAllRunningNotes(this.partition_id) this.form_note_text = '' this.form_categories = [] @@ -236,30 +199,12 @@ export const vRunningNotesTab = { showMarkdownHelp() { this.show_help = !this.show_help; }, - - countCards(category) { - if(category === 'All') { - return Object.values(this.running_notes).length - } - return Object.values(this.running_notes).filter(running_note => running_note.categories.includes(category)).length - }, - labelColor(category) { - if (category === 'All') { - return 'badge bg-secondary' - } - if(Object.values(cat_classes).some(subCat => subCat.hasOwnProperty(category))){ - const subCat = Object.values(cat_classes).find(subCat => subCat.hasOwnProperty(category)); - return 'badge bg-'+subCat[category][0]; - } - return '' - } }, mounted() { if ( !( ["flowcell", "workset", "flowcell_ont", "project"].includes(this.note_type))) { alert("Error: Invalid note type given, will not fetch notes") return } - this.fetchAllRunningNotes(this.partition_id); }, template: /*html*/`
@@ -402,8 +347,7 @@ export const vRunningNotesTab = {
ServerInstrument Disk (Used / Quota)
{{ server }}{{ status.get('instrument') }}
{{ status.get('space_used') }} {{ status.get('disk_size') }}
@@ -97,11 +82,13 @@

Server Status

- + + + - + {% end %} {% end %} diff --git a/run_dir/design/project_samples.html b/run_dir/design/project_samples.html index d2a1fde0a..7bced6490 100644 --- a/run_dir/design/project_samples.html +++ b/run_dir/design/project_samples.html @@ -9,9 +9,11 @@ {% block stuff %} +
-
- +
+ +
{% end %} diff --git a/run_dir/static/js/index.js b/run_dir/static/js/index.js index 57d6f9e78..f8cbced3b 100644 --- a/run_dir/static/js/index.js +++ b/run_dir/static/js/index.js @@ -41,93 +41,6 @@ const fill_sensorpush_status_field = () => { }); }; -const fill_prioprojs_table = () => { - //Get projects that have been waiting the longest - $.getJSON("api/v1/prio_projects", (data) => { - $("#prio_projs_table_body").empty(); - data.forEach((project) => { - const check_value = Math.abs(project[2]); - let day_color = ''; - let stat_color = ''; - let status = ''; - switch (project[1]) { - case 'days_recep_ctrl': - day_color = check_value > 7 ? (check_value > 14 ? 'text-danger' : 'text-orange') : 'text-success'; - stat_color = 'text-recep'; - status = 'In reception control'; - break; - case 'days_prep_start': - day_color = check_value > 7 ? (check_value > 10 ? 'text-danger': 'text-orange') :'text-success'; - stat_color = 'text-prep-start'; - status = 'To prep'; - break; - case 'days_prep': - day_color = check_value > 7 ? (check_value > 10 ? 'text-danger': 'text-orange') :'text-success'; - stat_color = 'text-prep'; - status = 'In prep'; - break; - case 'days_seq_start': - day_color = check_value > 7 ? (check_value > 10 ? 'text-danger': 'text-orange') :'text-success'; - stat_color = 'text_seq_start'; - status = 'To sequencing'; - break; - case 'days_seq': - day_color = check_value > 7 ? (check_value > 10 ? 'text-danger': 'text-orange') : 'text-success'; - stat_color = 'text-seq'; - status = 'In sequencing'; - break; - case 'days_analysis': - day_color = check_value > 7 ? (check_value > 10 ? 'text-danger': 'text-orange') : 'text-success'; - stat_color = 'text-analysis'; - status = 'In analysis'; - break; - case 'days_data_delivery': - day_color = check_value > 7 ? (check_value > 10 ? 'text-danger': 'text-orange') : 'text-success'; - stat_color = 'text-delivery'; - status = 'In delivery'; - break; - case 'days_close': - day_color = check_value > 7 ? (check_value > 10 ? 'text-danger': 'text-orange') : 'text-success'; - stat_color = 'text-close'; - status = 'To close'; - break; - } - const projectLibrary = project[0].split('|'); - const library = projectLibrary[1]; - const nameProjId = projectLibrary[0].replace('(', '').replace(')', '').split(' '); - const tblRow = `
${nameProjId[1]}${library}${status}${check_value}

For further reference, see the syntax document. - Live editing tools such as dillinger.io or - Mou may also be of use.

+ Live editing tools such as dillinger.io may also be of use.

@@ -412,51 +356,348 @@ export const vRunningNotesTab = {
- -
-
- - -
-
- - - -
-
+ - +

This is the notes

+ ` } +/* The component responsible for showing a list of running notes and filtering/searching */ +export const vRunningNotesList = { + name: 'v-running-notes-list', + props: { + user: null, + partition_id: null, + dynamic: false, // If false, no older running notes will be fetched + }, + data() { + return { + running_notes: {}, + projects_metadata: {}, + filter_choice: 'all', + include_production: true, + include_application: true, + include_internal: true, + include_control: true, + assigned_lab_responsible: true, + assigned_bioinfo_responsible: true, + assigned_project_coordinator: true, + assigned_bp_responsible: true, + include_self_written: false, + include_tagged: true, + search_term: '', + cat_classes: cat_classes, + category_filter: 'All', + } + }, + computed: { + current_user_email() { + return this.user.email; + }, + current_user_name() { + return this.user.user; + }, + // Filters + filterAll() { + // Do not apply any filter + return this.filter_choice === 'all'; + }, + filterOnAssigned() { + return this.filter_choice === 'assigned'; + }, + filterOnTagged() { + return this.filter_choice === 'tagged'; + }, + filterOnType() { + return this.filter_choice === 'type'; + }, + numberOfFetchedRunningNotes() { + return Object.keys(this.running_notes).length; + }, + numberOfVisibleRunningNotes() { + return this.visibleRunningNotes.length; + }, + userTaggedName() { + return this.current_user_email.split('@')[0]; + }, + visibleRunningNotes() { + var runningNotesArray = Object.values(this.running_notes); + + if (this.filterAll) { + // do nothing + } else if (this.filterOnTagged) { + runningNotesArray = runningNotesArray.filter(running_note => { + // Filter based on who created the running note + let self_written_bool = (this.include_self_written && running_note.email === this.current_user_email) + // Filter on who is tagged in the running note + let user_tagged_bool = (this.include_tagged && this.userTaggedName in this.taggedUsersFromRunningNote(running_note)) + + // Keep running notes if either of the above is true + return self_written_bool || user_tagged_bool; + }) + } else { + // Filter based on projects, filter the projects first + let projects_to_include = []; + if (this.filterOnType) { + for (let project in this.projects_metadata) { + if (this.include_production && this.projects_metadata[project].type === 'Production') { + projects_to_include.push(project); + continue + } + if (this.include_application && this.projects_metadata[project].type === 'Application') { + projects_to_include.push(project); + continue + } + if (this.include_other_types) { + projects_to_include.push(project); + continue + } + } + } else if (this.filterOnAssigned) { + for (let project in this.projects_metadata) { + if (this.assigned_lab_responsible && this.projects_metadata[project].lab_responsible === this.current_user_name) { + projects_to_include.push(project); + continue; + } + if (this.assigned_bioinfo_responsible && this.projects_metadata[project].bioinfo_responsible === this.current_user_name) { + projects_to_include.push(project); + continue; + } + if (this.assigned_project_coordinator && this.projects_metadata[project].project_coordinator === this.current_user_name) { + projects_to_include.push(project); + continue; + } + if (this.assigned_bp_responsible && this.projects_metadata[project].bp_responsible === this.current_user_name) { + projects_to_include.push(project); + continue; + } + } + } + + runningNotesArray = runningNotesArray.filter(running_note => { + return projects_to_include.includes(running_note.parent); + }); + } + + // Apply searching here + if (this.search_term !== '') { + runningNotesArray = runningNotesArray.filter(running_note => { + return (running_note.note.toLowerCase().includes(this.search_term.toLowerCase())) || + (running_note.user.toLowerCase().includes(this.search_term.toLowerCase())) || + (running_note.categories.join(' ').toLowerCase().includes(this.search_term.toLowerCase())) + }) + } + + // Filter by category + if (this.category_filter !== 'All') { + runningNotesArray = runningNotesArray.filter(running_note => { + return running_note.categories.includes(this.category_filter) + }) + } + + return runningNotesArray + } + }, + methods: { + async fetchAllRunningNotes(skip = 0) { + axios + .get(`/api/v1/latest_running_notes_with_meta?skip=${skip}&limit=20`) + .then(response => { + let data = response.data; + if (data !== null) { + this.running_notes = Object.assign({}, this.running_notes, data.running_notes); + this.projects_metadata = Object.assign({}, this.projects_metadata, data.projects_metadata); + } + }) + .catch(error => { + this.error_messages.push('Unable to fetch running notes, please try again or contact a system administrator.'); + }); + }, + fetchPartitionRunningNotes(partition_id) { + axios + .get('/api/v1/running_notes/' + partition_id) + .then(response => { + let data = response.data + if (data !== null) { + this.running_notes = data; + } + }) + .catch(error => { + this.$root.error_messages.push('Unable to fetch running notes, please try again or contact a system administrator.') + }) + }, + parent_metadata(running_note) { + return this.projects_metadata[running_note.parent] + }, + setFilter(filter) { + this.category_filter = filter + }, + taggedUsersFromRunningNote(running_note) { + const regex = /@([a-zA-Z0-9.-]+)/g; + const matches = running_note.note.matchAll(regex); + + const tagged_users = Array.from(matches, match => match[1]) + return tagged_users; + + }, + labelColor(category) { + if (category === 'All') { + return 'badge bg-secondary' + } + if(Object.values(cat_classes).some(subCat => subCat.hasOwnProperty(category))){ + const subCat = Object.values(cat_classes).find(subCat => subCat.hasOwnProperty(category)); + return 'badge bg-'+subCat[category][0]; + } + return '' + }, + countCards(category) { + if(category === 'All') { + return Object.values(this.running_notes).length + } + return Object.values(this.running_notes).filter(running_note => running_note.categories.includes(category)).length + } + }, + mounted() { + if (this.partition_id !== null) { + console.log("Fetching partition running notes for partition_id: " + this.partition_id); + this.fetchPartitionRunningNotes(this.partition_id); + } else { + this.fetchAllRunningNotes(0); + } + }, + template: /*html*/` +
+
+

Latest Running Notes Showing {{numberOfVisibleRunningNotes}} of {{numberOfFetchedRunningNotes}}

+
+ +
+
+ +
+
+ +
+
+
+ + + + + + +
+
+ + + + +
+ +
+ +
+ +
+
+ +
+ `, +} + export const vRunningNoteSingle = { - props: ['running_note_obj', 'compact', "partition_id", "uri_hash"], + props: ['running_note_obj', 'compact', "partition_id", "uri_hash", "display_link_to_parent", "parent_metadata"], data: function() { return { glowingCard: false @@ -535,12 +776,30 @@ export const vRunningNoteSingle = { let running_note_json = JSON.parse(this.running_note_obj) return Object.values(running_note_json)[0]; }, + link_to_partition() { + if (this.note_type === 'project') { + return '/project_new/' + this.partition_id + } + }, + metadata_link_title() { + let title = ''; + if (this.note_type === 'project') { + title = this.partition_id + if (this.parent_metadata !== undefined) { + title += ': ' + this.parent_metadata.project_name + } + } + return title + }, note_hash(){ return (new Date(this.created_at_utc).getTime()); }, note_id() { return 'running_note_'+this.partition_id+'_'+this.note_hash; }, + note_type() { + return this.getRunningNoteProperty('note_type') + }, email() { return this.getRunningNoteProperty('email') }, @@ -588,6 +847,10 @@ export const vRunningNoteSingle = {