From 5e07574c92e9c0d2f2665c5f7c25ff77c845e0c8 Mon Sep 17 00:00:00 2001 From: xpetrov4 <xpetrov4@mendelu.cz> Date: Mon, 5 Feb 2024 14:47:05 +0100 Subject: [PATCH] add cpg island page --- src/components/core/app-menu.vue | 7 + src/components/cpg/cpg-form.vue | 175 +++++++++++ src/components/cpg/cpg-results.vue | 288 ++++++++++++++++++ src/components/cpg/cpg.vue | 149 +++++++++ src/components/cpg/stored-results-details.vue | 55 ++++ src/components/cpg/stored-results.vue | 205 +++++++++++++ src/router.js | 20 ++ vue.config.js | 2 +- 8 files changed, 900 insertions(+), 1 deletion(-) create mode 100644 src/components/cpg/cpg-form.vue create mode 100644 src/components/cpg/cpg-results.vue create mode 100644 src/components/cpg/cpg.vue create mode 100644 src/components/cpg/stored-results-details.vue create mode 100644 src/components/cpg/stored-results.vue diff --git a/src/components/core/app-menu.vue b/src/components/core/app-menu.vue index f119c74..2f060ca 100644 --- a/src/components/core/app-menu.vue +++ b/src/components/core/app-menu.vue @@ -62,6 +62,13 @@ <b-dropdown-item :to="{ name: 'results.zdna' }" v-if="config.analysis.zdna"> Stored results (Z-DNA) </b-dropdown-item> + <div class="dropdown-divider" v-if="config.analysis.g4killer"></div> + <b-dropdown-item :to="{ name: 'analyse.cpg' }" v-if="config.analysis.cpg"> + CpG + </b-dropdown-item> + <b-dropdown-item :to="{ name: 'results.cpg' }" v-if="config.analysis.cpg"> + Stored results (CpG) + </b-dropdown-item> </b-nav-item-dropdown> <b-nav-item-dropdown text="Help"> diff --git a/src/components/cpg/cpg-form.vue b/src/components/cpg/cpg-form.vue new file mode 100644 index 0000000..cf43434 --- /dev/null +++ b/src/components/cpg/cpg-form.vue @@ -0,0 +1,175 @@ +<template> + <div class="row"> + <div class="col-md-12"> + <div class="form-group row"> + <label class="col-md-4 col-form-label">Minimal window size</label> + <div class="col-md-8"> + <input + type="number" + class="form-control" + v-model="minWindowSize" + step="1" + min="10" + max="10000" + @input="updateValue($event.target.value, 'minWindowSize')" + /> + </div> + </div> + + <div class="form-group row"> + <label class="col-md-4 col-form-label">Minimal GC percentage</label> + <div class="col-md-8"> + <input + type="number" + class="form-control" + v-model="minGcPercentage" + step="0.001" + min="0" + max="1" + @input="updateValue($event.target.value, 'minGcPercentage')" + /> + </div> + </div> + + <div class="form-group row"> + <label class="col-md-4 col-form-label">Minimal observed/expected CpG</label> + <div class="col-md-8"> + <input + type="number" + class="form-control" + v-model="minObservedToExpectedCpG" + step="0.001" + min="0" + max="1" + @input="updateValue($event.target.value, 'minObservedToExpectedCpG')" + /> + </div> + </div> + + <div class="form-group row"> + <label class="col-md-4 col-form-label">Minimal island merge gap</label> + <div class="col-md-8"> + <input + type="number" + class="form-control" + v-model="minIslandMergeGap" + step="1" + min="10" + max="10000" + @input="updateValue($event.target.value, 'minIslandMergeGap')" + /> + </div> + </div> + + <div class="form-group row"> + <label class="col-md-4 col-form-label">First nucleotide</label> + <div class="col-md-8"> + <input + type="text" + class="form-control" + v-model="firstNucleotide" + @input="updateValue($event.target.value, 'firstNucleotide')" + /> + </div> + </div> + + <div class="form-group row"> + <label class="col-md-4 col-form-label">Second nucleotide</label> + <div class="col-md-8"> + <input + type="text" + class="form-control" + v-model="secondNucleotide" + @input="updateValue($event.target.value, 'secondNucleotide')" + /> + </div> + </div> + + <div class="alert alert-danger" v-if="$v.$anyError"> + <div v-if="!$v.minSequenceSize.required">Minimal sequence size is required.</div> + <div v-if="!$v.minSequenceSize.numeric">Minimal sequence size must be numeric.</div> + <div v-if="!$v.minSequenceSize.minValue">Minimal length of sequence is {{ $v.minSequenceSize.$params.minValue.min }}.</div> + <div v-if="!$v.threshold.required">Threshold is required.</div> + <div v-if="!$v.threshold.decimal">Threshold must be decimal number.</div> + <div v-if="!$v.threshold.minValue">Minimal value of threshold is {{ $v.threshold.$params.minValue.min }}.</div> + </div> + + <div class="text-right"> + <slot name="additionalButtons"></slot> + </div> + </div> + </div> +</template> + +<script> +import { required, numeric, minValue, decimal } from 'vuelidate/lib/validators' + +export default { + props: { + minWindowSize: { + type: [Number, String], + default: 200 + }, + minGcPercentage: { + type: [Number, String], + default: 0.5 + }, + minObservedToExpectedCpG: { + type: [Number, String], + default: 0.6 + }, + minIslandMergeGap: { + type: [Number, String], + default: 100 + }, + firstNucleotide: { + type: [Number, String], + default: "C" + }, + secondNucleotide: { + type: [Number, String], + default: "G" + }, + }, + created() { + this.minSequenceSize = Number(this.minSequenceSize); + this.threshold = Number(this.threshold); + }, + validations: { + minWindowSize: { + required, + numeric, + minValue: minValue(0) + }, + minGcPercentage: { + required, + decimal, + minValue: minValue(0) + }, + minObservedToExpectedCpG: { + required, + decimal, + minValue: minValue(0) + }, + minIslandMergeGap: { + required, + numeric, + minValue: minValue(0) + }, + firstNucleotide: { + required, + }, + secondNucleotide: { + required, + }, + + }, + methods: { + updateValue: function(value, type) { + this[type] = value; // Aktualizácia lokálnej hodnoty + this.$v[type].$touch(); // Aktivuje validáciu + this.$emit('checkModel', value, type) + }, + }, +} +</script> diff --git a/src/components/cpg/cpg-results.vue b/src/components/cpg/cpg-results.vue new file mode 100644 index 0000000..ab621e5 --- /dev/null +++ b/src/components/cpg/cpg-results.vue @@ -0,0 +1,288 @@ +<template> + <div> + <h2 v-if="sequence"> + {{ sequence.name }} + <span v-if="sequence.name !== analysisInfo.title">: {{ analysisInfo.title }}</span> + <!-- slot for back button --> + <slot></slot> + </h2> + + <!-- overview --> + <div v-show="zdnaList"> + <heatchart + v-if="heatmapData" + :data="heatmapData" + y-axis-title="Count (Z-DNA)" + @dataPointClick="showSegment" + @onZoom="zoomHeatchart" + ></heatchart> + <heatmap v-if="heatmapData" :data="heatmapData" @segmentClick="showSegment" :selected="selectedSegment"> + </heatmap> + + <table class="table table-striped table-hover"> + <tbody> + <tr> + <th>Analysis settings</th> + <th>Analysis results</th> + <th class="text-center">Export</th> + <th v-if="sequence">Sequence info</th> + </tr> + <tr> + <td> + Minimal window size: {{ analysisInfo.minWindowSize | number(0) }} + <br /> + Minimal GC percentage: {{ analysisInfo.minGcPercentage }} + <br /> + Minimal observed/expected CpG: {{ analysisInfo.minObservedToExpectedCpG }} + <br /> + Minimal island merge gap: {{ analysisInfo.minIslandMergeGap }} + <br /> + First nucleotide: {{ analysisInfo.firstNucleotide }} + <br /> + Second nucleotide: {{ analysisInfo.secondNucleotide }} + </td> + <td> + Islands found: {{ analysisInfo.resultCount | number(0) }} + <br /> + <span v-if="sequence">Frequency: {{ (analysisInfo.resultCount / sequence.length) * 1000 | number(3) }} / 1000 bp</span> + </td> + <td class="text-center"> + <a v-if="downloadToken" :href="downloadCSV()" class="btn btn-primary btn-sm"> + <span class="fa fa-cube"></span> CSV + </a> + <a v-if="downloadToken" :href="downloadBED()" class="btn btn-primary btn-sm"> + <span class="fa fa-bed"></span> BedGraph + </a> + </td> + <td v-if="sequence"> + {{ sequence.name }} + <br /> + {{ sequence.length | number(0) }} bp + <br /> + GC: {{ GCCount }} ({{ GCRate | number(1) }}%) + </td> + </tr> + </tbody> + </table> + + <!-- list of results --> + <pagination ref="pagination" @pageChange="reload" :page-size="10"> + <table class="table table-striped table-hover"> + <tbody> + <tr> + <th> + <sort-toggle @sort="sortSequences" crit="start" :current="currentSort">Start</sort-toggle> + </th> + <th> + <sort-toggle @sort="sortSequences" crit="end" :current="currentSort">End</sort-toggle> + </th> + <th>Sequence</th> + <th> + <sort-toggle @sort="sortSequences" crit="gcPerc" :current="currentSort"> + GC Percentage + </sort-toggle> + </th> + <th> + <sort-toggle @sort="sortSequences" crit="observedToExpectedCpG" :current="currentSort"> + Observer/expected CpG + </sort-toggle> + </th> + <th class="text-center"> + <sort-toggle @sort="sortSequences" crit="length" :current="currentSort">Length</sort-toggle> + </th> + </tr> + <template v-for="zdna in zdnaList"> + <tr> + <td> + {{ zdna.position | number(0) }} + </td> + <td> + {{ zdna.end }} + </td> + <td class="sequence text-monospace text-bold"> + <highlight :sequence="zdna.sequence.slice(0, 20)+'...'"></highlight> + </td> + + <td class="sequence text-monospace">{{ zdna.gcPerc | number(3) }}</td> + <td class="sequence text-monospace">{{ zdna.observedToExpectedCpG | number(3) }}</td> + <td class="text-right"> + {{ zdna.length | number }} + </td> + </tr> + </template> + </tbody> + </table> + </pagination> + + <div v-show="zdnaList && zdnaList.length === 0"> + <p class="alert alert-warning"> + No CpG islands found. + </p> + </div> + </div> + </div> +</template> + +<script> +import Formatters from '@/formatters' +import SortToggle from '@/components/core/sort-toggle' +import Pagination from '@/components/core/pagination' +import SequenceStats from '@/components/sequence/helpers/sequence-stats' +import Helpers from '@/helpers' +import Heatchart from '@/components/core/visualisation/heatchart' +import Heatmap from '@/components/core/visualisation/heatmap' +import Highlight from '@/components/core/visualisation/highlight' + +/** + * list of results for sequence analysis + */ +export default { + props: ['analysisInfo', 'downloadToken'], + data() { + return { + sequence: null, + zdnaList: null, + avgZdnaLen: null, + totalZdnaLenM1: null, + totalZdnaLenM2: null, + heatmapData: null, + selectedSegment: undefined, + sequenceStart: undefined, + sequenceLength: undefined, + currentSort: { + crit: 'position', + asc: true, + }, + } + }, + methods: { + getSequenceInfo() { + return this.$http.get('sequence/' + this.analysisInfo.sequenceId).then(response => { + this.sequence = response.data.payload + }) + }, + countNucleic() { + this.$http.patch('sequence/' + this.analysisInfo.sequenceId + '/nucleic-counts').catch(err => { + this.httpError = err + }) + }, + reload() { + return this.$http + .get('analyse/cpg/' + this.analysisInfo.id + '/cpg', { + params: { + sort: this.currentSort.crit, + order: this.currentSort.asc ? 'ASC' : 'DESC', + sequenceStart: this.sequenceStart, + sequenceLength: this.sequenceLength, + pageSize: this.$refs.pagination.getPageSize(), + pageNumber: this.$refs.pagination.getPageCurrent() - 1, + }, + }) + .then(response => { + this.$refs.pagination.setPageCount(response.data.page.totalElements) + Helpers.setUIState(response.data.items, 'showDetails', false) + this.zdnaList = response.data.items + this.countNucleic() + }) + }, + toggleDetails(zdna) { + zdna.ui.showDetails = !zdna.ui.showDetails + }, + getAvgRizLen() { + return this.$http.get('analyse/cpg/' + this.analysisInfo.id + '/average/length').then(response => { + this.avgZdnaLen = response.data + }) + }, + + sortSequences(info) { + this.currentSort = info + this.reload() + }, + async downloadHeatmap() { + const total = this.sequence.length + const { sequenceFrom: from = 0, sequenceTo: to = total, analysisInfo } = this + const segments = 80 + const { data } = await this.$http.get(`analyse/cpg/${analysisInfo.id}/heatmap`, { + params: { segments, from, to }, + }) + + this.heatmapData = { ...data, from, to, total } + }, + /** + * click in heatmap + * + * @param seg + */ + showSegment(pos, size) { + if (this.selectedSegment === pos) { + this.selectedSegment = undefined + this.sequenceStart = undefined + this.sequenceLength = undefined + } else { + this.selectedSegment = pos + this.sequenceStart = Math.floor(pos * size) + this.sequenceLength = Math.ceil(size) + } + this.reload() + }, + async zoomHeatchart(from, to, onSuccess) { + this.sequenceFrom = from + this.sequenceTo = to + await this.downloadHeatmap() + onSuccess() + }, + downloadCSV() { + return `${this.$http.defaults.baseURL}analyse/cpg/${this.analysisInfo.id}/cpg.csv` + }, + downloadBED() { + return `${this.$http.defaults.baseURL}analyse/cpg/${this.analysisInfo.id}/cpg.bedgraph` + }, + }, + mounted() { + this.reload() + this.getSequenceInfo().then(() => { + this.getAvgRizLen().then(() => { + this.downloadHeatmap() + }) + }) + }, + computed: { + GCCount() { + return SequenceStats.calcGCCount(this.sequence) + }, + GCRate() { + return SequenceStats.calcGCRate(this.sequence) + }, + }, + components: { + highlight: Highlight, + 'sort-toggle': SortToggle, + pagination: Pagination, + heatmap: Heatmap, + heatchart: Heatchart, + }, + filters: { + number: Formatters.number, + genePos: Formatters.genePos, + }, +} +</script> + +<style scoped> +td, +th { + padding: 0.5rem; +} +td.sequence { + max-width: 400px; +} +td.details { + max-width: 600px; +} +td.details .feature { + border: 1px solid red; +} +td .toggle { + cursor: pointer; +} +</style> diff --git a/src/components/cpg/cpg.vue b/src/components/cpg/cpg.vue new file mode 100644 index 0000000..b872d5a --- /dev/null +++ b/src/components/cpg/cpg.vue @@ -0,0 +1,149 @@ +<template> + <div class="container-fluid"> + <h1>CpG tracker</h1> + <div class="row"> + <div class="col-md-4"> + <h2>Prediction models</h2> + <cpg-form @checkModel="updateModel" /> + </div> + <div class="col-md-8"> + <h2>Sequences</h2> + <sequence-selector ref="sequenceSelector" @analyse="startAnalysis" /> + </div> + </div> + + <div class="alert alert-danger" v-if="serverError">{{ serverError }}</div> + + <div v-if="waitError"> + <hr /> + <div class="alert alert-danger"> + <div class="row"> + <p class="col-10"> + Waiting for results took too long. + </p> + <div class="col-2"> + <button class="btn btn-primary btn-block" @click="waitForResult(analysisId)"> + <span class="fa fa-refresh"></span> Keep waiting + </button> + </div> + </div> + </div> + </div> + + <b-tabs v-if="finishedAnalysis.length > 0"> + <b-tab v-for="(analysis, i) of finishedAnalysis" :key="analysis.payload.id" :title="'Results ' + (i + 1)"> + <br /> + <results :analysis-info="analysis.payload" :download-token="analysis.downloadToken"> + <b-btn variant="danger" class="float-right" @click="closeResults(analysis.payload)"> + Close tab + </b-btn> + </results> + </b-tab> + </b-tabs> + + <hr /> + <div class="card bg-light"> + <div class="card-body"> + CpG analyser Tokai alg #TODO + </div> + </div> + </div> +</template> + +<script type="text/javascript"> +import SequenceSelector from '@/components/sequence/sequence-selector' +import BatchWatcher from '@/services/batch-watcher' +import CpgForm from './cpg-form' +import Results from './cpg-results' + + +export default { + data() { + return { + minWindowSize: 200, + minGcPercentage: 0.5, + minObservedToExpectedCpG: 0.6, + minIslandMergeGap: 100, + firstNucleotide: 'C', + secondNucleotide: 'G', + config: {}, + finishedAnalysis: [], + analysisId: null, + waiting: false, + waitError: false, + serverError: null, + } + }, + methods: { + closeResults(analysis) { + this.finishedAnalysis = this.finishedAnalysis.filter(info => { + return info.payload.id !== analysis.id + }) + }, + waitForResult(analysisId) { + this.waitError = false + this.waiting = true + var bw = new BatchWatcher(this.$http) + this.$store.commit('addWork', { key: analysisId, title: 'CpG analysis' }) + return bw + .wait('analyse/cpg/' + analysisId + '/analysis') + .then(response => { + this.finishedAnalysis.push(response.data) + }) + .finally(() => { + this.$store.commit('workDone', analysisId) + this.waiting = false + }) + }, + updateModel(value, type) { + if (type === 'minWindowSize') { + this.minWindowSize = value; + } else if (type === 'minGcPercentage') { + this.minGcPercentage = value; + } else if (type === 'minObservedToExpectedCpG') { + this.minObservedToExpectedCpG = value; + } else if (type === 'minIslandMergeGap') { + this.minIslandMergeGap = value; + } else if (type === 'firstNucleotide') { + this.firstNucleotide = value; + } else if (type === 'secondNucleotide') { + this.secondNucleotide = value; + } + }, + startAnalysis(sequence, callback) { + this.$http + .post('analyse/cpg', { + minWindowSize: this.minWindowSize, + minGcPercentage: this.minGcPercentage, + minObservedToExpectedCpG: this.minObservedToExpectedCpG, + minIslandMergeGap: this.minIslandMergeGap, + firstNucleotide: this.firstNucleotide, + secondNucleotide: this.secondNucleotide, + sequence: sequence.id, + tags: sequence.tags, + }) + .then(response => { + const item = response.data.payload + this.serverError = null + this.analysisId = item.id + return this.waitForResult(item.id) + }) + .then(() => { + callback() + }) + .catch(error => { + this.serverError = error?.response?.data?.message + callback('FAILED') + }) + }, + clearAnalysis() { + this.cpgAnalysis = [] + }, + }, + components: { + 'sequence-selector': SequenceSelector, + results: Results, + 'cpg-form': CpgForm, + }, +} +</script> diff --git a/src/components/cpg/stored-results-details.vue b/src/components/cpg/stored-results-details.vue new file mode 100644 index 0000000..f407c60 --- /dev/null +++ b/src/components/cpg/stored-results-details.vue @@ -0,0 +1,55 @@ +<template> + <div class="container-fluid"> + <div class="row"> + <div class="col-sm-10"> + <h1>CpG stored results</h1> + </div> + <div class="col-sm-2"> + <br /> + <router-link class="btn btn-primary btn-block" :to="{ name: 'results.cpg' }">Back</router-link> + </div> + </div> + + <results v-if="analysis" :analysis-info="analysis" :download-token="downloadToken"></results> + + <http-error :info="httpError"></http-error> + </div> +</template> + +<script> +import Results from './cpg-results' +import HttpError from '@/components/core/http-error' + +/** + * display previously stored results + */ +export default { + data() { + return { + analysis: null, + downloadToken: '', + httpError: '', + } + }, + methods: { + downloadResults() { + this.$http.get('analyse/cpg/' + this.$route.params.id + '/analysis').then( + response => { + this.analysis = response.data.payload + this.downloadToken = response.data.downloadToken + }, + err => { + this.httpError = err + } + ) + }, + }, + mounted() { + this.downloadResults() + }, + components: { + results: Results, + 'http-error': HttpError, + }, +} +</script> diff --git a/src/components/cpg/stored-results.vue b/src/components/cpg/stored-results.vue new file mode 100644 index 0000000..263df6e --- /dev/null +++ b/src/components/cpg/stored-results.vue @@ -0,0 +1,205 @@ +<template> + <div class="container"> + <h1>CpG results</h1> + + <pagination ref="pagination" @pageChange="downloadResults"> + <checkable-list ref="resultsList"> + <th slot="header">Sequence/Analyse name</th> + <th class="text-center" slot="header"> + <sort-toggle @sort="sortResults" crit="finished" :current="currentSort">Created</sort-toggle> + </th> + <th class="text-center" slot="header"> + <sort-toggle @sort="sortResults" crit="resultCount" :current="currentSort">Results count</sort-toggle> + </th> + <th class="text-center" slot="header">Tags</th> + <th class="text-center" slot="header">Export</th> + <th class="text-center" slot="header">Details</th> + <th class="text-center" slot="header">Edit</th> + <th class="text-center" slot="header">Delete</th> + + <span slot="item-name" slot-scope="props"> + {{ props.item.title }} + </span> + + <template slot-scope="props"> + <td class="text-right"> + <span v-if="props.item.finished">{{ props.item.finished | dateTime }}</span> + <span v-else class="fa fa-spinner animate"></span> + </td> + <td class="text-right"> + <span v-if="props.item.finished"> + {{ props.item.resultCount | number(0) }} + </span> + </td> + <td> + <b-badge v-for="tag in props.item.tags" :key="tag" pill variant="primary"> + {{ tag }} + </b-badge> + </td> + <td class="text-center"> + <a :href="downloadCSV(props.item)" v-if="downloadToken" class="btn btn-primary btn-sm"> + <span class="fa fa-cube"></span> + </a> + <a :href="downloadBED(props.item)" v-if="downloadToken" class="btn btn-primary btn-sm"> + <span class="fa fa-bed"></span> + </a> + </td> + <td> + <router-link + class="btn btn-primary btn-sm" + :to="{ name: 'results.cpg.detail', params: { id: props.item.id } }" + > + <span class="fa fa-search"></span> + </router-link> + </td> + <td class="text-center"> + <button class="btn btn-primary btn-sm" @click.stop="editResult(props.item)"> + <span class="fa fa-edit"></span> + </button> + </td> + <td class="text-center"> + <button class="btn btn-danger btn-sm" @click.stop="deleteResult(props.item)"> + <span class="fa fa-trash"></span> + </button> + </td> + </template> + + <p slot="no-items" class="alert alert-warning"> + No results found. + </p> + </checkable-list> + </pagination> + + <button type="button" class="btn btn-default" @click="toggleSelection"> + <i class="fa fa-refresh"></i> Invert selection + </button> + <button class="btn btn-danger" @click="deleteResults"><i class="fa fa-trash"></i> Delete selected</button> + + <modal-confirm ref="confirmModal">Really delete results?</modal-confirm> + <modal-confirm ref="confirmModalMulti">Really delete selected results?</modal-confirm> + + <modal-confirm ref="editModal"> + <template slot="header"> + Modify result + </template> + + <result-editor v-if="selectedResult" :result="selectedResult" ref="resultEditor"></result-editor> + + <template slot="buttonConfirm"> + Save changes + </template> + </modal-confirm> + </div> +</template> +<script> +import SortToggle from '@/components/core/sort-toggle' +import Formatters from '@/formatters' +import CheckableList from '@/components/core/checkable-list' +import Pagination from '@/components/core/pagination' +import ResultEditor from '@/components/core/result-editor' + +/** + * list of stored result groups + */ +export default { + data() { + return { + selectedResult: null, + currentSort: { + crit: 'finished', + asc: true, + }, + downloadToken: '', + } + }, + methods: { + downloadResults() { + this.$http + .get('analyse/cpg', { + params: { + sort: this.currentSort.crit, + order: this.currentSort.asc ? 'ASC' : 'DESC', + pageSize: this.$refs.pagination.getPageSize(), + pageNumber: this.$refs.pagination.getPageCurrent() - 1, + }, + }) + .then(response => { + this.$refs.pagination.setPageCount(response.data.page.totalElements) + this.$refs.resultsList.setItems(response.data.items) + this.downloadToken = response.data.downloadToken + }) + }, + performDelete(result) { + return this.$http.delete('analyse/cpg/' + result.id) + }, + deleteResult(result) { + this.$refs.confirmModal + .display() + .then(() => { + return this.performDelete(result) + }) + .then(() => { + this.downloadResults() + }) + }, + deleteResults() { + this.$refs.confirmModalMulti.display().then(() => { + var checked = this.$refs.resultsList.getCheckedItems() + var promises = [] + checked.forEach(result => { + if (result.ui.checked) { + promises.push(this.performDelete(result)) + } + }) + Promise.all(promises).then(() => { + this.downloadResults() + }) + }) + }, + editResult(result) { + this.selectedResult = result + this.$refs.editModal + .display() + .then(() => { + this.$http + .put('analyse/cpg/' + result.id + '/tags', { + tags: this.$refs.resultEditor.getTags(), + }) + .then(() => { + this.downloadResults() + }) + }) + .finally(() => { + this.selectedResult = null + }) + }, + sortResults(info) { + this.currentSort = info + this.downloadResults() + }, + toggleSelection() { + this.$refs.resultsList.toggleSelection() + }, + downloadCSV(analysisInfo) { + return `${this.$http.defaults.baseURL}analyse/cpg/${analysisInfo.id}/cpg.csv` + }, + downloadBED(analysisInfo) { + return `${this.$http.defaults.baseURL}analyse/cpg/${analysisInfo.id}/cpg.bedgraph` + }, + }, + mounted() { + this.downloadResults() + }, + components: { + 'sort-toggle': SortToggle, + 'checkable-list': CheckableList, + pagination: Pagination, + 'result-editor': ResultEditor, + }, + filters: { + dateTime: Formatters.dateTime, + number: Formatters.number, + sequenceName: Formatters.sequenceName, + }, +} +</script> diff --git a/src/router.js b/src/router.js index 63efc56..111a07c 100644 --- a/src/router.js +++ b/src/router.js @@ -35,6 +35,10 @@ import Zdna from "./components/zdna/zdna"; import ZdnaResults from "./components/zdna/stored-results"; import ZdnaResultsDetail from './components/zdna/stored-results-details'; +import Cpg from "./components/cpg/cpg"; +import CpgResults from "./components/cpg/stored-results"; +import CpgResultsDetail from './components/cpg/stored-results-details'; + Vue.use(Router) var router = new Router({ @@ -177,6 +181,22 @@ var router = new Router({ path: '/results/zdna/:id', component: ZdnaResultsDetail, }, + //CpG + { + name: 'analyse.cpg', + path: '/analyse/cpg', + component: Cpg, + }, + { + name: 'results.cpg', + path: '/results/cpg', + component: CpgResults, + }, + { + name: 'results.cpg.detail', + path: '/results/cpg/:id', + component: CpgResultsDetail, + }, //help routes { diff --git a/vue.config.js b/vue.config.js index 4d5400c..f90f248 100644 --- a/vue.config.js +++ b/vue.config.js @@ -4,7 +4,7 @@ module.exports = { devServer: { proxy: { '/api': { - target: 'https://localhost:443', + target: 'http://localhost:8080', ws: true, changeOrigin: true, } -- GitLab