...
 
Commits (3)
......@@ -8,7 +8,7 @@
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
<link rel="stylesheet" href="https://cdn.materialdesignicons.com/2.1.19/css/materialdesignicons.min.css">
<script defer src="https://use.fontawesome.com/releases/v5.0.8/js/all.js" integrity="sha384-SlE991lGASHoBfWbelyBPLsUlwY1GwNDJo3jSJO04KZ33K2bwfV9YBauFfnzvynJ" crossorigin="anonymous"></script>
<link href="https://unpkg.com/vuetify@1.0.19/dist/vuetify.min.css" rel="stylesheet">
<link href="https://unpkg.com/vuetify@1.5.16/dist/vuetify.min.css" rel="stylesheet">
<link rel="stylesheet" href="${pageContext.request.contextPath}/resources/css/main.css?timestamp=${timestamp}">
......@@ -21,7 +21,7 @@
<script src="//cdnjs.cloudflare.com/ajax/libs/Vue.Draggable/2.14.1/vuedraggable.min.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<script src="https://unpkg.com/vuetify@1.0.19/dist/vuetify.min.js"></script>
<script src="https://unpkg.com/vuetify@1.5.16/dist/vuetify.min.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
......
......@@ -169,4 +169,37 @@ table.table tbody td:first-child, table.table tbody td:not(:first-child), table.
.pointer {
cursor: pointer;
}
\ No newline at end of file
}
.menu-image .v-toolbar__content, .v-toolbar__extension {
padding-left: 0px;
padding-right: 0px;
padding-top: 5px;
padding-bottom: 0px;
}
.toolbar__title {
overflow: visible;
}
.small-solo-input .v-text-field.v-text-field--solo .v-input__control {
min-height: 36px;
min-height: 24px;
max-width: 60px;
}
.toolbar-slider {
padding-top: 18px;
}
.toolbar-radio-group .v-input__control .v-input__slot {
margin-bottom: 0px;
}
.toolbar-radio-label .v-label{
white-space: nowrap;
}
.checkbox-input-slot-bottom-margin .v-input__control .v-input__slot {
margin-bottom: 0px;
}
......@@ -18,20 +18,19 @@ const CompareCoverage = {
<div class="pb-2">
<v-toolbar dark color="primary" fixed app>
<v-toolbar-title class="white--text">Compare Coverage</v-toolbar-title>
<v-spacer></v-spacer>
<div class="title white--text pl-3 pr-1 ">Show:</div>
<v-radio-group v-model="isTier1" v-on:change="getAjaxData" class="mt-2">
<v-radio dark label="All Exons" value="allExons" class="title white--text"></v-radio>
<v-radio dark label="Tier 1 Only" value="tier1Only" input-value="true" class="title white--text"></v-radio>
<v-radio-group v-model="isTier1" v-on:change="getAjaxData" class="mt-2 toolbar-radio-group">
<v-radio dark label="All Exons" value="allExons" class="title white--text toolbar-radio-label"></v-radio>
<v-radio dark label="Tier 1 Only" value="tier1Only" input-value="true" class="title white--text toolbar-radio-label"></v-radio>
</v-radio-group>
<v-radio-group v-model="uniqueOnly" v-on:change="getAjaxData" class="mt-2 pl-2">
<v-radio dark label="Dups" value="dups" class="title white--text"></v-radio>
<v-radio dark label="Uniq" value="uniq" input-value="true" class="title white--text"></v-radio>
</v-radio-group>
<v-radio-group v-model="uniqueOnly" v-on:change="getAjaxData" class="mt-2 pl-2 toolbar-radio-group">
<v-radio dark label="Dups" value="dups" class="title white--text toolbar-radio-label"></v-radio>
<v-radio dark label="Uniq" value="uniq" input-value="true" class="title white--text toolbar-radio-label"></v-radio>
</v-radio-group>
<v-spacer></v-spacer>
......@@ -75,22 +74,22 @@ const CompareCoverage = {
</v-tooltip>
</v-card-title>
<v-card-text class="pt-0">
<v-select :clearable="true" ref="sampleSelect_0" auto v-on:input="getAjaxData" :search-input.sync="populateSamples_0" :items="sampleItems[0].sampleNames"
v-model="sampleItems[0].sampleId" item-text="name" item-value="value" label="Type Sample 1 Name" single-line autocomplete
<v-autocomplete :clearable="true" ref="sampleSelect_0" auto v-on:input="getAjaxData" :search-input.sync="populateSamples_0" :items="sampleItems[0].sampleNames"
v-model="sampleItems[0].sampleId" item-text="name" item-value="value" label="Type Sample 1 Name" single-line
light :no-data-text="sampleItems[0].noDataText">
</v-select>
<v-select :clearable="true" ref="sampleSelect_1" auto v-on:input="getAjaxData" :search-input.sync="populateSamples_1" :items="sampleItems[1].sampleNames"
v-model="sampleItems[1].sampleId" item-text="name" item-value="value" label="Type Sample 2 Name" single-line autocomplete
</v-autocomplete>
<v-autocomplete :clearable="true" ref="sampleSelect_1" auto v-on:input="getAjaxData" :search-input.sync="populateSamples_1" :items="sampleItems[1].sampleNames"
v-model="sampleItems[1].sampleId" item-text="name" item-value="value" label="Type Sample 2 Name" single-line
light :no-data-text="sampleItems[1].noDataText">
</v-select>
<v-select :clearable="true" ref="sampleSelect_2" auto v-on:input="getAjaxData" :search-input.sync="populateSamples_2" :items="sampleItems[2].sampleNames"
v-model="sampleItems[2].sampleId" item-text="name" item-value="value" label="Type Sample 3 Name" single-line autocomplete
</v-autocomplete>
<v-autocomplete :clearable="true" ref="sampleSelect_2" auto v-on:input="getAjaxData" :search-input.sync="populateSamples_2" :items="sampleItems[2].sampleNames"
v-model="sampleItems[2].sampleId" item-text="name" item-value="value" label="Type Sample 3 Name" single-line
light :no-data-text="sampleItems[2].noDataText">
</v-select>
<v-select :clearable="true" ref="sampleSelect_3" auto v-on:input="getAjaxData" :search-input.sync="populateSamples_3" :items="sampleItems[3].sampleNames"
v-model="sampleItems[3].sampleId" item-text="name" item-value="value" label="Type Sample 4 Name" single-line autocomplete
</v-autocomplete>
<v-autocomplete :clearable="true" ref="sampleSelect_3" auto v-on:input="getAjaxData" :search-input.sync="populateSamples_3" :items="sampleItems[3].sampleNames"
v-model="sampleItems[3].sampleId" item-text="name" item-value="value" label="Type Sample 4 Name" single-line
light :no-data-text="sampleItems[3].noDataText">
</v-select>
</v-autocomplete>
</v-card-text>
</v-card>
</v-flex>
......@@ -397,4 +396,4 @@ const CompareCoverage = {
this.loadSamples(query, 3);
}
}
};
\ No newline at end of file
};
......@@ -20,28 +20,96 @@ Vue.component('data-table', {
"show-pagination": { default: true, type: Boolean },
"show-row-count": { default: false, type: Boolean },
"title-icon": { default: null, type: String },
"show-left-menu": { default: true, type: Boolean },
"disable-sticky-header": {default: false, type: Boolean},
"additional-headers": {default: () => [], type: Array}, //for situations where add a new row needs more fields than in headers
"color": { default: "primary", type: String }
},
template: `<div>
<!-- Top tool bar with menu options -->
<v-toolbar dark color="primary" :fixed="fixed" :app="fixed" v-show="toolbarVisible">
<v-icon v-if="titleIcon">{{ titleIcon }}</v-icon>
<v-toolbar-title class="white--text">{{ tableTitle }}
<!-- icon with no function -->
<v-icon v-if="titleIcon && !showLeftMenu" :color="iconColor ? iconColor : 'amber accent-2'">{{ titleIcon }}</v-icon>
<!-- menu with same functions as left side icons -->
<v-tooltip class="ml-0" bottom v-if="showLeftMenu">
<v-menu offset-y offset-x slot="activator" class="ml-0">
<v-btn slot="activator" flat icon dark>
<v-icon v-if="!titleIcon">more_vert</v-icon>
<v-icon v-if="titleIcon" :color="iconColor ? iconColor : 'amber accent-2'">{{ titleIcon }}</v-icon>
</v-btn>
<v-list>
<slot name="action1MenuItem"></slot>
<slot name="action2MenuItem"></slot>
<slot name="action3MenuItem"></slot>
<v-list-tile avatar @click="toggleSearchBar">
<v-list-tile-avatar>
<v-icon>search</v-icon>
</v-list-tile-avatar>
<v-list-tile-content>
<v-list-tile-title>Quick Filter</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
<v-list-tile avatar v-if="advanceFiltering" @click="toggleFilters">
<v-list-tile-avatar>
<v-icon>filter_list</v-icon>
</v-list-tile-avatar>
<v-list-tile-content>
<v-list-tile-title>Filter Menu</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
<v-list-tile avatar v-if="exportEnabled" @click="exportToCSV">
<v-list-tile-avatar>
<v-icon>file_download</v-icon>
</v-list-tile-avatar>
<v-list-tile-content></v-list-tile-content>
<v-list-tile-title>Export to CSV</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
<v-list-tile avatar @click="showDraggableHeader=!showDraggableHeader">
<v-list-tile-avatar>
<v-icon>swap_horiz</v-icon>
</v-list-tile-avatar>
<v-list-tile-content>
<v-list-tile-title>Move Columns</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
<v-list-tile avatar @click="handleRefresh()">
<v-list-tile-avatar>
<v-icon>refresh</v-icon>
</v-list-tile-avatar>
<v-list-tile-content>
<v-list-tile-title>Refresh</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
</v-list>
</v-menu>
<span>Table Menu</span>
</v-tooltip>
<v-toolbar-title class="white--text toolbar__title">{{ tableTitle }}
<span v-if="showRowCount" v-html="getRowCount()"></span>
</v-toolbar-title>
<v-spacer></v-spacer>
<v-layout justify-end>
<v-layout justify-end align-center>
<v-flex class="text-xs-right">
<v-container>
<slot name="title"></slot>
</v-container>
</v-flex>
<v-flex xs7 class="text-xs-right" v-show="showPagination">
<div class="title white--text pr-1 pt-4 mt-1">Rows:</div>
<v-flex xs6 class="text-xs-right" v-show="showPagination">
<div class="title white--text">Rows:</div>
</v-flex>
<v-flex xs3 v-show="showPagination">
<v-flex xs4 v-show="showPagination">
<v-container>
<v-text-field solo single-line hide-details light v-model="pagination.rowsPerPage"></v-text-field>
</v-container>
......@@ -403,10 +471,20 @@ Vue.component('data-table', {
highlight: null, //use this to change the style of a row should have the value of item.[uniqueIdField]
doExport: false, //if true, the table will be exported as a CSV
csvContent: "",
headerOptionsVisible: false //work in progress
headerOptionsVisible: false, //work in progress
newRow: {}
}
},
methods: {
getButtonColor(flag) {
if (!flag) {
return "white";
}
if (this.iconActiveColor) {
return this.iconActiveColor;
}
return 'amber accent-2';
},
toggleAll() {
if (this.selected.length) this.selected = []
else this.selected = this.items.slice()
......@@ -1025,8 +1103,6 @@ Vue.component('data-table', {
descending: this.sortDescending,
rowsPerPage: 10
}
},
destroyed: function () {
window.removeEventListener('scroll', this.handleScroll);
......
......@@ -24,7 +24,7 @@ Vue.component('login', {
required
dark
:append-icon="showPasswordIcon ? 'visibility' : 'visibility_off'"
:append-icon-cb="() => (showPasswordIcon = !showPasswordIcon)"
@click:append="() => (showPasswordIcon = !showPasswordIcon)"
:type="showPasswordIcon ? 'password' : 'text'"
></v-text-field>
</div>
......@@ -125,4 +125,4 @@ Vue.component('login', {
})
\ No newline at end of file
})
......@@ -4,10 +4,16 @@ Vue.component('main-menu', {
baseUrl: {type: String, default: webAppRoot},
width: {type: Number, default: 200}
},
template: `<v-navigation-drawer app permanent :width="width" :mini-variant.sync="isMinied">
<v-toolbar flat :extended="isMinied ? false : true" style="height:128px">
template: `
<v-navigation-drawer app permanent :width="width" :mini-variant.sync="isMinied">
<v-toolbar flat :extended="isMinied ? false : true" width="100%" class="menu-image">
<img :src="baseUrl + '/resources/images/nuclia-logo-medium.png'" alt="NuCLIA" width="100%" :class="['pl-2', 'pr-2', isMinied ? '' : 'pt-5 mt-2']"/>
</v-toolbar>
<v-divider></v-divider>
<v-list dense class="pt-0">
<v-list-tile>
......@@ -42,8 +48,8 @@ Vue.component('main-menu', {
</v-btn>
<v-card v-if="menuItem.projectOrderSearch">
<v-select v-on:input="loadProjectOrderDetails" v-bind:items="projectOrders" v-model="projectOrderItemSelected" item-text="name"
item-value="value" label="Order Name" single-line solo autocomplete></v-select>
<v-autocomplete hide-details v-on:input="loadProjectOrderDetails" v-bind:items="projectOrders" v-model="projectOrderItemSelected" item-text="name" clearable
item-value="value" label="Order Name" single-line solo autocomplete></v-autocomplete>
</v-card>
</v-menu>
......@@ -138,8 +144,8 @@ Vue.component('main-menu', {
bus.$on('need-layout-resize', args => {
//resize the main content because it does not happen automatically
this.$nextTick(function() {
document.getElementsByClassName("content")[0].style.paddingLeft = this.width + "px";
var titlebars = document.getElementsByClassName("toolbar toolbar--fixed");
document.getElementsByClassName("v-content")[0].style.paddingLeft = this.width + "px";
var titlebars = document.getElementsByClassName("v-toolbar v-toolbar--fixed");
for (var i = 0; i < titlebars.length; i++) {
titlebars[i].style.paddingLeft = this.width + "px";
}
......@@ -156,5 +162,5 @@ Vue.component('main-menu', {
watch: {
isMinied: 'emitMenuChanged'
}
});
\ No newline at end of file
});
......@@ -39,9 +39,10 @@ const Help = {
</p>
<div class="subheading">
<v-tabs v-model="currentTab" color="primary" slider-color="warning" dark fixed-tabs>
<v-tab ripple>Tables</v-tab>
<v-tab ripple>Charts</v-tab>
<v-tab-item v-model="currentTab" class="pt-2">
<v-tab ripple href="#tablesTab">Tables</v-tab>
<v-tab ripple href="#chartsTab">Charts</v-tab>
<v-tab-item v-model="currentTab" value="tablesTab" class="pt-2">
<p>All tables work in similar ways. The toolbar controls the number of rows displayed.
<br/> The buttons on the right side activate various functions such as simple filtering, changing the order
of columns, and refreshing of the data.
......@@ -58,7 +59,7 @@ const Help = {
<v-icon color="green">check_circle</v-icon>
<v-icon color="red">cancel</v-icon>. Hover the header of a column to know what the threshold is.
</v-tab-item>
<v-tab-item v-model="currentTab" class="pt-2">
<v-tab-item v-model="currentTab" value="chartsTab" class="pt-2">
<p>Most charts are interactive. You can hover a data point with the mouse to get more information.</p>
<div id="exampleChart"></div>
<p>Charts usually have a navigation bar at the bottom to zoom in and out.
......
......@@ -34,7 +34,7 @@ const Home = {
</v-dialog>
<v-toolbar dark color="primary" app>
<v-toolbar-title class="white--text">Sample Overview
<v-toolbar-title class="white--text toolbar__title">Sample Overview
</v-toolbar-title>
<v-tabs color="primary" slider-color="warning" v-model="activeTab" centered>
<v-tab href="#tab-tat" @click="activeTab = 'tab-tat'">Turn Around Times</v-tab>
......@@ -59,7 +59,7 @@ const Home = {
</v-btn>
</v-toolbar>
<v-tabs-items v-model="activeTab">
<v-tab-item id="tab-tat">
<v-tab-item value="tab-tat">
<div :style="fullSizeChart" class="chart-border">
<div id="tat" style="height: 100%"></div>
</div>
......@@ -70,7 +70,7 @@ const Home = {
</data-table>
</v-tab-item>
<v-tab-item id="tab-tat-samples">
<v-tab-item value="tab-tat-samples">
<div :style="fullSizeChart" class="chart-border">
<div id="tatSamplesChart" style="height: 100%"></div>
</div>
......@@ -80,13 +80,13 @@ const Home = {
</data-table>
</v-tab-item>
<v-tab-item id="tab-timesTaken">
<v-tab-item value="tab-timesTaken">
<div :style="fullSizeChart" class="chart-border">
<div id="timesTaken" style="height: 100%"></div>
</div>
</v-tab-item>
<v-tab-item id="tab-alignment">
<v-tab-item value="tab-alignment">
<data-table ref="monthlyDnaAlignment" :fixed="false" :fetch-on-created="false" @need-manual-data="getMonthlySummary('monthlyDnaAlignment', 'getMonthlyDnaAlignmentSummary')"
:table-title="dnaAlignmentTableTitle + selectedMonthFormatted" :initial-sort="'limsId'" no-data-text="No Data"
class="pt-2">
......
const LowCoverageBrowser = {
template: `<div>
<div class="pb-2">
<v-toolbar dark color="primary" app absolute>
<v-toolbar-title class="white--text">Low Coverage Browser
</v-toolbar-title>
<v-spacer></v-spacer>
</v-toolbar>
</div>
template: `
<div>
<div class="pb-2">
<v-toolbar dark color="primary" fixed app>
<v-toolbar-title class="white--text">Low Coverage Browser</v-toolbar-title>
<v-spacer></v-spacer>
</v-toolbar>
</div>
<v-layout row>
<v-flex xs3>
<v-card>
<v-card-title primary-title>
<div class="subheading">Search for Genes</div>
</v-card-title>
<v-card-text>
<v-text-field auto autofocus auto-grow textarea label="Insert Gene List Here"
v-model="originalList">
</v-text-field>
<v-text-field label="Low Cov Threshold" v-model="threshold"></v-text-field>
<v-btn @click="startSearch" :disabled="loading"
>
<v-progress-circular indeterminate v-if="loading"></v-progress-circular>
<span v-if="!loading">Search</span>
</v-btn>
</v-card-text>
</v-card>
</v-flex>
<v-flex xs9 class="pl-2">
<v-card>
<data-table ref="geneTable" table-title="Low Coverage"
no-data-text="Enter list of genes on the left"
:toolbar-visible="false"
:fetch-on-created="false"
initial-sort="geneSymbols"></data-table>
<v-layout row>
<v-flex xs3>
<v-card>
<v-card-title primary-title>
<div class="subheading">Search for Genes</div>
</v-card-title>
<v-card-text>
<v-textarea auto autofocus auto-grow outline label="Insert Gene List Here"
v-model="originalList">
</v-textarea>
<v-text-field label="Low Cov Threshold" v-model="threshold"></v-text-field>
<v-btn @click="startSearch" :disabled="loading"
>
<v-progress-circular indeterminate v-if="loading"></v-progress-circular>
<span v-if="!loading">Search</span>
</v-btn>
</v-card-text>
</v-card>
</v-flex>
</v-layout>
</div>`, data() { return { loading: false,
</v-flex>
<v-flex xs9 class="pl-2">
<v-card>
<data-table ref="geneTable" table-title="Low Coverage"
no-data-text="Enter list of genes on the left"
:toolbar-visible="false"
:fetch-on-created="false"
initial-sort="geneSymbols"></data-table>
</v-card>
</v-flex>
</v-layout>
</div>`
, data() { return { loading: false,
originalList: "",
threshold: 100,
uniqueOnly: false
......@@ -89,4 +90,4 @@ const LowCoverageBrowser = {
},
watch: {
}
};
\ No newline at end of file
};
......@@ -51,7 +51,7 @@ const OrderDetails = {
<span>Scroll to the beginning</span>
</v-tooltip>
<v-slider :thumb-color="'white'" v-if="samples.length > 4" :track-color="'white'" :color="'white'" v-model="scrollPos" step="0"
:max="maxScrollWidth" class="mt-2 pr-0 pl-0">
:max="maxScrollWidth" class="toolbar-slider">
</v-slider>
<v-tooltip bottom v-if="samples.length > 4">
<v-btn icon @click="scrollPos = maxScrollWidth" slot="activator">
......@@ -311,7 +311,7 @@ const OrderDetails = {
})
.then(response => {
if (response.data.isAllowed) {
//in case the projectOrder has empty samples
// in case the projectOrder has empty samples
for (var i = 0; i < response.data.samples.length; i++) {
if (response.data.samples[i] == null) {
response.data.samples[i] = {};
......@@ -361,7 +361,8 @@ const OrderDetails = {
this.tableIndexActive = -1;
this.headerIndexActive = -1;
},
// TODO add more values here if needed. Maybe add that property to Header instead?
// TODO add more values here if needed. Maybe add that property to
// Header instead?
isValueTooLong(headerValue) {
return headerValue == 'sampleLabName' || headerValue == 'libDnaBCIndex'
|| headerValue == 'seqRunId' || headerValue == 'correlatedSampleName'
......@@ -411,28 +412,28 @@ const OrderDetails = {
return itemString;
},
// showQCFlag(header, item) {
// return header.isPassable && this.formattedItem(header, item);
// return header.isPassable && this.formattedItem(header, item);
// },
showTitle(sample, title) {
//no indicator for DNA/RNA
// no indicator for DNA/RNA
if (title.indexOf("DNA") == -1 && title.indexOf("RNA") == -1) {
//could be Somatic (DNA only)
// could be Somatic (DNA only)
return (title != 'Somatic' || title == 'Somatic' && sample.sampleType.indexOf("DNA") > -1);
}
//presence of DNA/RNA in title
// presence of DNA/RNA in title
return (sample.sampleType.indexOf("DNA") > -1 && title.indexOf("DNA") > -1)
|| (sample.sampleType.indexOf("RNA") > -1 && title.indexOf("RNA") > -1);
},
// createOrderDetailsPDF() {
// var samplesVisible = [];
// for (var i = 0; i < this.samples.length; i++) {
// samplesVisible.push(this.samples[i].sampleId);
// }
// var sampleIds = samplesVisible.join(",");
// window.location = webAppRoot + "/createOrderDetailsPDF?subjectId="
// + this.$route.params.id
// + "&sampleIdsParam=" + sampleIds;
// },
// createOrderDetailsPDF() {
// var samplesVisible = [];
// for (var i = 0; i < this.samples.length; i++) {
// samplesVisible.push(this.samples[i].sampleId);
// }
// var sampleIds = samplesVisible.join(",");
// window.location = webAppRoot + "/createOrderDetailsPDF?subjectId="
// + this.$route.params.id
// + "&sampleIdsParam=" + sampleIds;
// },
showPassFlag(header, item) {
return header.isPassable === true
&& item[header.value]
......@@ -464,10 +465,10 @@ const OrderDetails = {
}).then(response => {
this.createPDFFile(response.data);
// this.exportLoading = false;
// this.exportLoading = false;
}
).catch(error => {
if (error.response.status == 403) { //need to relogin
if (error.response.status == 403) { // need to relogin
bus.$emit("login-needed", [this, this.createOrderDetailsPDF]);
}
else {
......@@ -501,7 +502,8 @@ const OrderDetails = {
this.handleDialogs(response, this.getFusionData);
}
this.loading = false;
// this.$nextTick(() => {this.calcTableMaxWidth() }); //not used anymore
// this.$nextTick(() => {this.calcTableMaxWidth() }); //not
// used anymore
})
.catch(error => {
this.loading = false;
......@@ -522,7 +524,8 @@ const OrderDetails = {
this.handleDialogs(response, this.getSNSPData);
}
this.loading = false;
// this.$nextTick(() => {this.calcTableMaxWidth() }); //not used anymore
// this.$nextTick(() => {this.calcTableMaxWidth() }); //not
// used anymore
})
.catch(error => {
this.loading = false;
......@@ -545,4 +548,4 @@ const OrderDetails = {
'$route': 'getAjaxData',
'scrollPos': 'scrollWithBar'
}
};
\ No newline at end of file
};
......@@ -68,8 +68,8 @@ const QMetrics = {
</v-flex>
<v-flex xs6>
<div class="pt-4">
<v-checkbox :disabled="loading" class="no-height" label="FFPE" v-model="tissueFFPE" @change="getQualityMetrics()"></v-checkbox>
<v-checkbox :disabled="loading" class="no-height" label="Fresh" v-model="tissueFresh" @change="getQualityMetrics()"></v-checkbox>
<v-checkbox :disabled="loading" class="no-height ma-0 pa-0 checkbox-input-slot-bottom-margin" label="FFPE" v-model="tissueFFPE" @change="getQualityMetrics()"></v-checkbox>
<v-checkbox :disabled="loading" class="no-height ma-0 pa-0 checkbox-input-slot-bottom-margin" label="Fresh" v-model="tissueFresh" @change="getQualityMetrics()"></v-checkbox>
</div>
</v-flex>
<v-flex xs10>
......
......@@ -8,19 +8,19 @@ const SampleCoverage = {
<div>
<v-toolbar dark color="primary" fixed app>
<v-toolbar-title class="white--text">Sample Coverage</v-toolbar-title>
<v-toolbar-title class="white--text toolbar__title">Sample Coverage</v-toolbar-title>
<v-spacer></v-spacer>
<div class="title white--text pl-4 pr-2 ">Show:</div>
<v-radio-group v-model="isTier1" v-on:change="handleRouteChanged" class="mt-2" style="max-width: 150px">
<v-radio dark label="All Exons" value="allExons" class="title white--text"></v-radio>
<v-radio dark label="Tier 1 Only" value="tier1Only" input-value="true" class="title white--text"></v-radio>
<v-radio-group v-model="isTier1" v-on:change="handleRouteChanged" class="mt-2 toolbar-radio-group" style="max-width: 150px" column>
<v-radio dark label="All Exons" value="allExons" class="title white--text toolbar-radio-label"></v-radio>
<v-radio dark label="Tier 1 Only" value="tier1Only" input-value="true" class="title white--text toolbar-radio-label"></v-radio>
</v-radio-group>
<v-radio-group v-model="uniqueOnly" v-on:change="handleRouteChanged" class="mt-2 pl-2" style="max-width: 150px">
<v-radio dark label="Raw" value="dups" class="title white--text"></v-radio>
<v-radio dark label="Uniq" value="uniq" input-value="true" class="title white--text"></v-radio>
<v-radio-group v-model="uniqueOnly" v-on:change="handleRouteChanged" class="mt-2 pl-2 toolbar-radio-group" style="max-width: 150px" column>
<v-radio dark label="Raw" value="dups" class="title white--text toolbar-radio-label"></v-radio>
<v-radio dark label="Uniq" value="uniq" input-value="true" class="title white--text toolbar-radio-label"></v-radio>
</v-radio-group>
<!-- <v-tooltip bottom>
......@@ -59,11 +59,11 @@ const SampleCoverage = {
<v-container grid-list-md fluid>
<v-layout row wrap>
<v-flex xs4>
<v-select ref="sampleSelect" auto v-on:input="handleRouteChanged" :search-input.sync="populateSamples"
<v-autocomplete ref="sampleSelect" auto v-on:input="handleRouteChanged" :search-input.sync="populateSamples"
:items="sampleNames" v-model="sampleItemSelected" item-text="name" item-value="value" label="Type a Sample Name"
autocomplete light :disabled="inputDisabled" :loading="populatingSampleSelect"
light :disabled="inputDisabled" :loading="populatingSampleSelect"
:no-data-text="noDataText"
offset-y></v-select>
offset-y></v-autocomplete>
</v-flex>
<v-flex xs4 offset-xs1>
<!-- TODO add a select for seq run and add that to ajax calls and queries as well -->
......@@ -104,7 +104,7 @@ const SampleCoverage = {
<v-flex v-show="histogramVisible" :class="[histogramVisible ? getFlexClass() : '']">
<v-card>
<v-toolbar dark color="primary">
<div class="title white--text pl-3 pr-1">Grouped by Coverage</div>
<div class="title white--text pr-1">Grouped by Coverage</div>
<v-spacer></v-spacer>
<v-tooltip bottom>
<v-btn flat icon slot="activator" @click="histogramVisible = false">
......@@ -143,7 +143,7 @@ const SampleCoverage = {
<v-flex v-show="distributionVisible" :class="[distributionVisible ? getFlexClass() : '']">
<v-card>
<v-toolbar dark color="primary">
<div class="title white--text pl-3 pr-1 ">Order's Distribution</div>
<div class="title white--text pr-1">Order's Distribution</div>
<v-spacer></v-spacer>
<v-tooltip bottom>
<v-btn flat icon slot="activator" @click="distributionVisible = false">
......
......@@ -8,15 +8,15 @@ const SampleDetailsTable = { template:
Show:
</v-flex>
<v-flex>
<v-radio-group v-model="fail" @change="reloadTable" class="mt-2">
<v-radio dark label="All Samples" value="allSamples" input-value="true" class="title white--text"></v-radio>
<v-radio dark label="Fail Only" value="failOnly" class="title white--text"></v-radio>
<v-radio-group v-model="fail" @change="reloadTable" class="mt-2 toolbar-radio-group">
<v-radio dark label="All Samples" value="allSamples" input-value="true" class="title white--text toolbar-radio-label"></v-radio>
<v-radio dark label="Fail Only" value="failOnly" class="title white--text toolbar-radio-label"></v-radio>
</v-radio-group>
</v-flex>
</v-layout> -->
<v-radio-group v-model="fail" @change="reloadTable" class="mt-2">
<v-radio dark label="All Samples" value="allSamples" input-value="true" class="title white--text"></v-radio>
<v-radio dark label="Fail Only" value="failOnly" class="title white--text"></v-radio>
<v-radio-group v-model="fail" @change="reloadTable" class="mt-2 toolbar-radio-group">
<v-radio dark label="All Samples" value="allSamples" input-value="true" class="title white--text toolbar-radio-label"></v-radio>
<v-radio dark label="Fail Only" value="failOnly" class="title white--text toolbar-radio-label"></v-radio>
</v-radio-group>
</div>
</data-table>`,
......@@ -32,4 +32,4 @@ methods: {
});
}
}
};
\ No newline at end of file
};
......@@ -21,21 +21,20 @@ const SeqRunDetailsTable = {
</v-toolbar>
<v-layout row wrap v-show="isFilterVisible">
<v-flex xs4>
<v-select auto clearable :search-input.sync="populateSeqRuns" :items="seqRunIds" v-model="seqRunIdSelected" item-text="name" item-value="value"
label="Seq Run Id" autocomplete></v-select>
<v-autocomplete auto clearable :search-input.sync="populateSeqRuns" :items="seqRunIds" v-model="seqRunIdSelected" item-text="name" item-value="value"
label="Seq Run Id"></v-autocomplete>
</v-flex>
<v-flex xs4 class="pl-2">
<v-select auto clearable :items="machineNames" v-model="machineNameSelected" item-text="name" item-value="value" label="Machine Name"
autocomplete></v-select>
<v-autocomplete auto clearable :items="machineNames" v-model="machineNameSelected" item-text="name" item-value="value" label="Machine Name"></v-autocomplete>
</v-flex>
<v-flex xs4 class="pl-2">
<v-select auto clearable :search-input.sync="populateSamples" :items="sampleNames" v-model="sampleSelected" item-text="name" item-value="value"
label="Sample Name" autocomplete></v-select>
<v-autocomplete auto clearable :search-input.sync="populateSamples" :items="sampleNames" v-model="sampleSelected" item-text="name" item-value="value"
label="Sample Name"></v-autocomplete>
</v-flex>
<v-flex xs3 class="pl-2">
<v-menu lazy :close-on-content-click="true" v-model="fromDateVisible" transition="scale-transition" offset-y full-width :nudge-right="40"
max-width="290px" min-width="290px">
<v-text-field slot="activator" label="From MM/DD/YY" v-model="fromDateFormatted" append-icon="event" @blur="fromDate = parseDate(fromDateFormatted)"></v-text-field>
<v-text-field clearable slot="activator" label="From MM/DD/YY" v-model="fromDateFormatted" append-icon="event" @blur="fromDate = parseDate(fromDateFormatted)"></v-text-field>
<v-date-picker v-model="fromDate" @input="fromDateFormatted = formatDate($event)" no-title scrollable>
</v-date-picker>
</v-menu>
......@@ -317,4 +316,4 @@ const SeqRunDetailsTable = {
}
}
};
\ No newline at end of file
};
......@@ -2,6 +2,8 @@ package utsw.bicf.nucliavault.controller;
import java.io.IOException;
import java.text.ParseException;
import java.time.LocalDate;
import java.util.Enumeration;
import java.util.List;
import javax.servlet.ServletContext;
......@@ -10,18 +12,26 @@ import javax.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import utsw.bicf.nucliavault.clarity.api.utils.APIResponse;
import utsw.bicf.nucliavault.clarity.api.utils.TypeUtils;
import utsw.bicf.nucliavault.controller.serialization.AjaxResponse;
import utsw.bicf.nucliavault.controller.serialization.TargetPage;
import utsw.bicf.nucliavault.controller.serialization.vuetify.ExampleTableSummary;
import utsw.bicf.nucliavault.controller.serialization.vuetify.SampleDetailsTableSummary;
import utsw.bicf.nucliavault.controller.serialization.vuetify.SampleToImportSummary;
import utsw.bicf.nucliavault.controller.serialization.vuetify.Summary;
import utsw.bicf.nucliavault.controller.serialization.vuetify.UserTableSummary;
import utsw.bicf.nucliavault.dao.ModelDAO;
import utsw.bicf.nucliavault.dao.SampleDAO;
import utsw.bicf.nucliavault.dao.SaveOrUpdateDAO;
......@@ -36,7 +46,7 @@ import utsw.bicf.nucliavault.security.ClarityDBConnection;
@Controller
@RequestMapping("/")
public class AdminController {
@Autowired
ServletContext servletContext;
@Autowired
......@@ -129,4 +139,105 @@ public class AdminController {
return ajaxResponse.createObjectJSON();
}
@RequestMapping("/getUserDataTable")
@ResponseBody
public String getUserDataTable(Model model, HttpSession session) throws Exception {
NucliaUser currentUser = (NucliaUser) session.getAttribute("user");
if (currentUser != null && !currentUser.getAdmin()) {
TargetPage targetPage = new TargetPage(model);
return targetPage.toJSONString();
}
try {
UserTableSummary summary = new UserTableSummary("userId", modelDAO);
return summary.createVuetifyObjectJSON();
} catch (JsonProcessingException e) {
e.printStackTrace();
return null;
}
}
@RequestMapping(value = "/saveUser", method = RequestMethod.POST)
@ResponseBody
public String saveUser(Model model, HttpSession session, @RequestBody String data, @RequestParam String userId) throws Exception {
NucliaUser currentUser = (NucliaUser) session.getAttribute("user");
AjaxResponse ajaxResponse = new AjaxResponse();
if (currentUser != null && !currentUser.getAdmin()) {
TargetPage targetPage = new TargetPage(model);
ajaxResponse.setIsAllowed(false);
return targetPage.toJSONString();
}
if (userId.isEmpty()) { // Save a new user
try {
ObjectMapper mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
NucliaUser newUser = mapper.readValue(data, NucliaUser.class);
modelDAO.saveObject(newUser);
ajaxResponse.setSuccess(true);
ajaxResponse.setMessage("Successfully saved new user");
} catch (Exception e) {
e.printStackTrace();
ajaxResponse.setSuccess(false);
ajaxResponse.setMessage("Error saving new user.");
}
} else { // Update a user
try {
ObjectMapper mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
NucliaUser newData = mapper.readValue(data, NucliaUser.class);
NucliaUser userToUpdate = modelDAO.getUserById(userId);
userToUpdate.setFirst(newData.getFirst());
userToUpdate.setLast(newData.getLast());
userToUpdate.setEmail(newData.getEmail());
userToUpdate.setUsername(newData.getUsername());
userToUpdate.setCanEdit(newData.getCanEdit());
userToUpdate.setAdmin(newData.getAdmin());
modelDAO.saveObject(userToUpdate);
ajaxResponse.setSuccess(true);
ajaxResponse.setMessage("Successfully updated user.");
} catch (Exception e) {
e.printStackTrace();
ajaxResponse.setSuccess(false);
ajaxResponse.setMessage("Error updating user.");
}
}
return ajaxResponse.createObjectJSON();
}
@RequestMapping(value = "/deleteUser", method = RequestMethod.POST)
@ResponseBody
public String deleteUser(Model model, HttpSession session, @RequestParam String userId) throws Exception {
NucliaUser currentUser = (NucliaUser) session.getAttribute("user");
AjaxResponse ajaxResponse = new AjaxResponse();
if (currentUser != null && !currentUser.getAdmin()) {
TargetPage targetPage = new TargetPage(model);
ajaxResponse.setIsAllowed(false);
return targetPage.toJSONString();
}
// Attempt to delete the user
try {
NucliaUser userToDelete = modelDAO.getUserById(userId);
if (userToDelete != null) {
modelDAO.deleteObject(userToDelete);
ajaxResponse.setSuccess(true);
ajaxResponse.setMessage("Successfully deleted user.");
} else {
ajaxResponse.setSuccess(false);
ajaxResponse.setMessage("Could not fetch the user to delete by userId.");
}
} catch (Exception e) {
e.printStackTrace();
ajaxResponse.setSuccess(false);
ajaxResponse.setMessage("Error deleting user.");
}
return ajaxResponse.createObjectJSON();
}
}
package utsw.bicf.nucliavault.controller.serialization.vuetify;
import java.util.List;
import java.util.stream.Collectors;
import utsw.bicf.nucliavault.controller.serialization.ToolTip;
import utsw.bicf.nucliavault.dao.ModelDAO;
import utsw.bicf.nucliavault.model.hybrid.UserTable;
//JSON Object with
//headerOrder: array of the headers in the order we want them displayed
//items: array of json objects containing the data with the keys matching the header's value property
//headers: array of json objects (value, text)
public class UserTableSummary extends Summary<UserTable> {
public UserTableSummary(String uniqueIdField, ModelDAO modelDAO) {
super(getUserSummaryData(modelDAO), uniqueIdField);
}
private static List<UserTable> getUserSummaryData(ModelDAO modelDAO) {
List<UserTable> dataRows = modelDAO.getUserSummaryData(modelDAO);
return dataRows;
}
@Override
public void initializeHeaders() {
Header nameHeader = new Header("Name", "fullName");
nameHeader.setWidth("250px");
headers.add(nameHeader);
Header emailHeader = new Header("Email", "email");
emailHeader.setWidth("250px");
headers.add(emailHeader);
Header userNameHeader = new Header("Username", "userName");
userNameHeader.setWidth("50px");
headers.add(userNameHeader);
Header canEditHeader = new Header("Can Edit", "canEditValue");
canEditHeader.setWidth("25px");
canEditHeader.setToolTip(new ToolTip("Can a user edit other user information?"));
canEditHeader.setIsPassable(true);
headers.add(canEditHeader);
Header isAdminHeader = new Header("Admin", "isAdminValue");
isAdminHeader.setWidth("25px");
isAdminHeader.setToolTip(new ToolTip("Does a user have admin access?"));
isAdminHeader.setIsPassable(true);
headers.add(isAdminHeader);
Header userActions = new Header("Edit User", "buttons");
userActions.setWidth("25px");
userActions.setButtons(true);
userActions.setAlign("center");
headers.add(userActions);
//keep in the same order
headerOrder = headers.stream().map(header -> header.getValue()).collect(Collectors.toList());
}
}
......@@ -42,12 +42,14 @@ import utsw.bicf.nucliavault.model.SeqRun;
import utsw.bicf.nucliavault.model.SomaticStats;
import utsw.bicf.nucliavault.model.TechnicianMarker;
import utsw.bicf.nucliavault.model.Threshold;
import utsw.bicf.nucliavault.model.NucliaUser;
import utsw.bicf.nucliavault.model.hybrid.LowCoverageTable;
import utsw.bicf.nucliavault.model.hybrid.MonthlyAlignmentTable;
import utsw.bicf.nucliavault.model.hybrid.MonthlySampleTAT;
import utsw.bicf.nucliavault.model.hybrid.SampleDetailsTable;
import utsw.bicf.nucliavault.model.hybrid.SeqRunDetailsTable;
import utsw.bicf.nucliavault.model.hybrid.TimeTakenPerOrder;
import utsw.bicf.nucliavault.model.hybrid.UserTable;
@Repository
public class ModelDAO {
......@@ -61,6 +63,11 @@ public class ModelDAO {
sessionFactory.getCurrentSession().saveOrUpdate(object);
}
@Transactional
public void deleteObject(Object object) {
sessionFactory.getCurrentSession().delete(object);
}
@Transactional
public DnaExtract getDnaExtractByLimsId(String limsId) {
Session session = sessionFactory.getCurrentSession();
......@@ -831,5 +838,31 @@ public class ModelDAO {
return covViewers;
}
@Transactional
public List<UserTable> getUserSummaryData(ModelDAO modelDAO) {
Session session = sessionFactory.getCurrentSession();
String sql = "select * from nuclia_user";
List<NucliaUser> users = session.createNativeQuery(sql, NucliaUser.class).list();
List<UserTable> userRows = new ArrayList<UserTable>();
for(NucliaUser user : users) {
userRows.add(new UserTable(user));
}
return userRows;
}
@Transactional
public NucliaUser getUserById(String userId) {
Session session = sessionFactory.getCurrentSession();
String sql = "select * from nuclia_user where nuclia_user_id = :userId";
List<NucliaUser> users = session.createNativeQuery(sql, NucliaUser.class)
.setParameter("userId", Integer.parseInt(userId)).list();
if (users != null && users.size() == 1) {
return users.get(0);
}
return null;
}
}
......@@ -10,7 +10,6 @@ import javax.persistence.Table;
@Entity
@Table(name="nuclia_user")
public class NucliaUser {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
......@@ -29,9 +28,24 @@ public class NucliaUser {
@Column(name="can_edit")
Boolean canEdit;
@Column(name="email")
String email;
@Column(name="admin")
Boolean admin;
public NucliaUser() { }
public NucliaUser(String first, String last, String username, Boolean canEdit, String email, Boolean admin) {
super();
this.first = first;
this.last = last;
this.username = username;
this.canEdit = canEdit;
this.email = email;
this.admin = admin;
}
public Integer getNucliaUserId() {
return nucliaUserId;
}
......@@ -72,6 +86,14 @@ public class NucliaUser {
this.canEdit = canEdit;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Boolean getAdmin() {
return admin;
}
......@@ -84,4 +106,5 @@ public class NucliaUser {
public String toString() {
return first + " " + last;
}
}
package utsw.bicf.nucliavault.model.hybrid;
import java.text.ParseException;
import java.util.List;
import java.util.ArrayList;
import utsw.bicf.nucliavault.clarity.api.utils.TypeUtils;
import utsw.bicf.nucliavault.controller.serialization.PassableValue;
import utsw.bicf.nucliavault.model.NucliaUser;
import utsw.bicf.nucliavault.controller.serialization.Button;
/**
* Base object to represent a hybrid mapping of tables related to sample
* extraction/library. This class is used by Summary to create a JSON string of
* its fields or just with JSP to display the field values.
*
* @author Jason Hough
*
*/
public class UserTable {
Integer userId;
String firstName;
String lastName;
String fullName;
String userName;
Boolean canEdit;
PassableValue canEditValue;
String email;
Boolean isAdmin;
PassableValue isAdminValue;
List<Button> buttons = new ArrayList<Button>();
public UserTable(Object[] values, Object[] labels) throws ParseException {
super();
for (int i = 0; i < labels.length; i++) {
String label = (String) labels[i];
// Cases match NucliaUser.java's variable @Column name
switch(label) {
case "nuclia_user_id":
userId = (Integer) values[i];
break;
case "first":
firstName = (String) values[i];
fullName = firstName;
break;
case "last":
lastName = (String) values[i];
fullName = fullName + " " + lastName;
break;
case "username":
userName = (String) values[i];
break;
case "can_edit":
canEdit = (Boolean) TypeUtils.intToBoolean(((Byte) values[i]).intValue());
canEditValue = new PassableValue("canEditValue", "", canEdit == true);
break;
case "email":
email = (String) values[i];
break;
case "admin":
isAdmin = (Boolean) TypeUtils.intToBoolean(((Byte) values[i]).intValue());
isAdminValue = new PassableValue("isAdminValue", "", isAdmin == true);
break;
}
}
buttons.add(new Button("create", "editUser", "Edit User", "info"));
buttons.add(new Button("delete_forever", "deleteUser", "Delete User", "red"));
}
public UserTable(NucliaUser user) {
this.userId = user.getNucliaUserId();
this.firstName = user.getFirst();
this.lastName = user.getLast();
this.fullName = firstName + " " + lastName;
this.userName = user.getUsername();
this.canEdit = user.getCanEdit();
this.canEditValue = new PassableValue("canEditValue", "", canEdit == true);
this.email = user.getEmail();
this.isAdmin = user.getAdmin();
this.isAdminValue = new PassableValue("isAdminValue", "", isAdmin == true);
buttons.add(new Button("create", "editUser", "Edit User", "info"));
buttons.add(new Button("delete_forever", "deleteUser", "Delete User", "red"));
}
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getFullName() {
return fullName;
}
public void setFullName(String fullName) {
this.fullName = fullName;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public Boolean getCanEdit() {
return canEdit;
}
public void setCanEdit(Boolean canEdit) {
this.canEdit = canEdit;
}
public PassableValue getCanEditValue() {
return canEditValue;
}
public void setCanEditValue(PassableValue canEditValue) {
this.canEditValue = canEditValue;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Boolean getIsAdmin() {
return isAdmin;
}
public void setIsAdmin(Boolean isAdmin) {
this.isAdmin = isAdmin;
}
public PassableValue getIsAdminValue() {
return isAdminValue;
}
public void setIsAdminValue(PassableValue isAdminValue) {
this.isAdminValue = isAdminValue;
}
public List<Button> getButtons() {
return buttons;
}
public void setButtons(List<Button> buttons) {
this.buttons = buttons;
}
@Override
public String toString() {
return "UserTable [userId=" + userId + ", fullName=" + fullName + ", userName=" + userName + ", canEdit=" + canEdit
+ ", canEditValue=" + canEditValue + ", email=" + email + ", isAdmin=" + isAdmin + ", isAdminValue="
+ isAdminValue + "]";
}
}
......@@ -26,7 +26,7 @@ public class ControlFusionParser {
continue;
}
String[] items = row.split("\t");
if (items != null && items.length == 9) {
if (items != null && items.length == 16) {
ControlFusionParser fusion = new ControlFusionParser();
fusion.setFusionName(items[getHeaderIndex(HEADER_FUSION_NAME, headers)]);
fusion.setRnaReads(Integer.parseInt(items[getHeaderIndex(HEADER_RNA_READS, headers)]));
......