Commit b3708040 authored by Sascha Herzinger's avatar Sascha Herzinger
Browse files

New demo page, improved UX/UI for initial chart states, FirefoxHeadless

parent 55a10075
Pipeline #4684 failed with stages
in 1 minute and 4 seconds
......@@ -11,10 +11,13 @@ stages:
test:all:
stage: test
script:
- yum install -y epel-release git
- yum install -y epel-release
- yum update -y
- echo -e "[google-chrome]\nname=google-chrome\nbaseurl=http://dl.google.com/linux/chrome/rpm/stable/\$basearch\nenabled=1\ngpgcheck=1\ngpgkey=https://dl-ssl.google.com/linux/linux_signing_key.pub" > /etc/yum.repos.d/google-chrome.repo
- yum install -y npm google-chrome-stable
- yum install -y git wget bzip2 firefox
- rm /usr/bin/firefox
- wget -O firefox "https://download.mozilla.org/?product=firefox-latest&os=linux64&lang=en-US"
- tar jxf firefox.tar.bz2
- ln -s /firefox/firefox /usr/local/bin/firefox
- npm install
- npm test
......
const webpackConfig = require('./webpack.config.js')
process.env.CHROME_BIN = require('puppeteer').executablePath()
module.exports = config => {
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine'],
files: [
{pattern: 'test/**/*-test.js'}
'test/test-index.js'
],
exclude: [
],
browsers: ['NoSandboxHeadlessChrome'],
customLaunchers: {
NoSandboxHeadlessChrome: {
base: 'ChromeHeadless',
flags: ['--no-sandbox']
}
},
preprocessors: {
'test/**/*-test.js': ['webpack', 'sourcemap']
'test/test-index.js': ['webpack', 'sourcemap']
},
webpack: webpackConfig,
webpackMiddleware: {
......@@ -25,8 +19,13 @@ module.exports = config => {
color: true
}
},
reporter: ['progress'],
reporters: ['spec'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: false,
browsers: ['FirefoxHeadless'],
singleRun: true,
colors: true
concurrency: Infinity
})
}
This diff is collapsed.
......@@ -149,7 +149,7 @@
import ControlPanel from '../components/ControlPanel.vue'
import Chart from '../components/Chart.vue'
import store from '../../store/store'
import runAnalysis from '../../utils/run-analysis'
import RunAnalysis from '../mixins/run-analysis'
import * as d3 from 'd3'
import deepFreeze from 'deep-freeze-strict'
import tooltip from '../directives/tooltip'
......@@ -411,7 +411,7 @@
this.height = height
},
runAnalysisWrapper (args) {
runAnalysis('compute-boxplot', args)
this.runAnalysis('compute-boxplot', args)
.then(response => {
const results = JSON.parse(response)
results.data = JSON.parse(results.data)
......@@ -427,7 +427,8 @@
Chart
},
mixins: [
StateSaver
StateSaver,
RunAnalysis
],
directives: {
tooltip
......
......@@ -104,7 +104,7 @@
import { getPolygonPointsForSubset } from '../../utils/utils'
import Chart from '../components/Chart.vue'
import store from '../../store/store'
import runAnalysis from '../../utils/run-analysis'
import RunAnalysis from '../mixins/run-analysis'
import * as d3 from 'd3'
import tooltip from '../directives/tooltip.js'
import deepFreeze from 'deep-freeze-strict'
......@@ -420,12 +420,12 @@
tooltip
},
mixins: [
StateSaver
StateSaver,
RunAnalysis
],
methods: {
runAnalysisWrapper (init, args) {
// function made available via requestHandling mixin
runAnalysis('compute-correlation', args)
this.runAnalysis('compute-correlation', args)
.then(response => {
const results = JSON.parse(response)
results.data = JSON.parse(results.data)
......
......@@ -176,7 +176,7 @@
import ControlPanel from '../components/ControlPanel.vue'
import Chart from '../components/Chart.vue'
import store from '../../store/store'
import runAnalysis from '../../utils/run-analysis'
import RunAnalysis from '../mixins/run-analysis'
import * as d3 from 'd3'
import tooltip from '../directives/tooltip.js'
import deepFreeze from 'deep-freeze-strict'
......@@ -428,7 +428,7 @@
},
methods: {
computeHeatmap (args) {
runAnalysis('compute-heatmap', args)
this.runAnalysis('compute-heatmap', args)
.then(response => {
const results = JSON.parse(response)
deepFreeze(results) // massively improve performance by telling Vue that the objects properties won't change
......@@ -436,7 +436,7 @@
})
},
computeCluster (args) {
runAnalysis('compute-cluster', args)
this.runAnalysis('compute-cluster', args)
.then(response => {
const results = JSON.parse(response)
deepFreeze(results)
......@@ -493,7 +493,8 @@
tooltip
},
mixins: [
StateSaver
StateSaver,
RunAnalysis
],
mounted () {
this.registerDataToSave([
......
......@@ -115,7 +115,7 @@
import Chart from '../components/Chart.vue'
import { getPolygonPointsForSubset } from '../../utils/utils'
import store from '../../store/store'
import runAnalysis from '../../utils/run-analysis'
import RunAnalysis from '../mixins/run-analysis'
import * as d3 from 'd3'
import tooltip from '../directives/tooltip.js'
import deepFreeze from 'deep-freeze-strict'
......@@ -361,7 +361,7 @@
},
methods: {
runAnalysisWrapper (args) {
runAnalysis('compute-pca', args)
this.runAnalysis('compute-pca', args)
.then(response => {
const results = JSON.parse(response)
deepFreeze(results) // massively improve performance by telling Vue that the objects properties won't change
......@@ -426,7 +426,8 @@
tooltip
},
mixins: [
StateSaver
StateSaver,
RunAnalysis
],
mounted () {
this.registerDataToSave([
......
<template>
<div class="fjs-chart" @mousedown.capture="focusControlPanel">
<div class="fjs-init-cover" @click="animate" ref="cover" v-show="showCover">
<div>
<span>Select</span>
</div>
</div>
<slot/>
</div>
</template>
......@@ -10,7 +15,8 @@
name: 'chart',
data () {
return {
observer: null
observer: null,
init: true
}
},
mounted () {
......@@ -23,6 +29,11 @@
this.observer.disconnect()
window.removeEventListener('load', this.resize)
},
computed: {
showCover () {
return this.$parent.$data.__init
}
},
methods: {
resize () {
const width = this.$el.getBoundingClientRect().width - 1
......@@ -34,15 +45,47 @@
focusControlPanel () {
const controlPanel = this.$children.find(d => d.$options.name === 'control-panel')
controlPanel.focus()
},
animate () {
this.$refs.cover.classList.add('fjs-animate')
window.setTimeout(() => this.$refs.cover.classList.remove('fjs-animate'), 300)
}
}
}
</script>
<style lang="sass" scoped>
@keyframes fjs-effect-click
0%
background: #fff
50%
background: #e6e6e6
100%
background: #fff
.fjs-chart
width: 100%
height: 100%
&:hover
box-shadow: inset 0 0 0 2px #29D5FF
position: relative
.fjs-animate
animation: fjs-effect-click 300ms ease-in
.fjs-init-cover
display: table
height: 100%
width: 100%
position: absolute
top: 0
left: 0
z-index: 10
background: white
cursor: pointer
&:hover
box-shadow: inset 0 0 0 2px #e6e6e6
div
display: table-cell
vertical-align: middle
text-align: center
span
font-size: 1.5em
background: #e6e6e6
padding: 10px 20px 10px 20px
</style>
import store from '../store/store'
import store from '../../store/store'
/**
* A helper method to submit an analysis.
......@@ -12,61 +12,69 @@ import store from '../store/store'
* selected data ids. Note that Arrays of '$' wrapped strings are also valid.
* @returns {Promise.<void>} An ES6 promise. Resolves into the result of the analysis.
*/
async function runAnalysis (taskName, args) {
function timeout (ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}
const rv = await store.getters.requestManager.createAnalysis(taskName, args)
const taskID = rv.data.task_id
store.dispatch('setTask', {
taskID,
taskName,
taskState: 'SUBMITTED'
})
export default {
data () {
return {
__init: true
}
},
methods: {
async runAnalysis (taskName, args) {
function timeout (ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}
let timeWaited = 0
let delay = 200
while (timeWaited <= 900000) { // we wait 15 minutes
await timeout(delay)
timeWaited += delay
delay += 100
delay = delay > 3000 ? 3000 : delay
const rv2 = await store.getters.requestManager.getAnalysisStatus(taskID)
const taskInfo = rv2.data
if (taskInfo.state === 'SUCCESS') {
const rv = await store.getters.requestManager.createAnalysis(taskName, args)
const taskID = rv.data.task_id
store.dispatch('setTask', {
taskID,
taskName,
taskState: taskInfo.state
taskState: 'SUBMITTED'
})
return taskInfo.result
} else if (taskInfo.state === 'FAILURE') {
let timeWaited = 0
let delay = 200
while (timeWaited <= 900000) { // we wait 15 minutes
await timeout(delay)
timeWaited += delay
delay += 100
delay = delay > 3000 ? 3000 : delay
const rv2 = await store.getters.requestManager.getAnalysisStatus(taskID)
const taskInfo = rv2.data
if (taskInfo.state === 'SUCCESS') {
store.dispatch('setTask', {
taskID,
taskName,
taskState: taskInfo.state
})
this.$data.__init = false // current component is no longer in its initial state
return taskInfo.result
} else if (taskInfo.state === 'FAILURE') {
store.dispatch('setTask', {
taskID,
taskName,
taskState: taskInfo.state,
taskMessage: taskInfo.result
})
throw new Error(taskInfo.result)
} else if (taskInfo.state === 'SUBMITTED') {
store.dispatch('setTask', {
taskID,
taskName,
taskState: taskInfo.state})
} else {
throw new Error(`Analysis Task has unhandled state: ${taskInfo.state}`)
}
}
const error = 'Analysis took too long. Stopped listener.'
store.dispatch('setTask', {
taskID,
taskName,
taskState: taskInfo.state,
taskMessage: taskInfo.result
taskState: 'FAILURE',
taskMessage: error
})
throw new Error(taskInfo.result)
} else if (taskInfo.state === 'SUBMITTED') {
store.dispatch('setTask', {
taskID,
taskName,
taskState: taskInfo.state})
} else {
throw new Error(`Analysis Task has unhandled state: ${taskInfo.state}`)
throw new Error(error)
}
}
const error = 'Analysis took too long. Stopped listener.'
store.dispatch('setTask', {
taskID,
taskName,
taskState: 'FAILURE',
taskMessage: error
})
throw new Error(error)
}
// This code is here because of: https://github.com/babel/babel/issues/3786
export default runAnalysis
......@@ -6,19 +6,30 @@
</head>
<body>
<input type="button" onclick="loadData()" value="load data"/>
<input type="button" onclick="deleteData()" value="delete data"/>
<input type="button" onclick="loadState()" value="load state"/>
<input type="text" class="state-ids" style="width: 50vw;">
<div class="containers">
<h1>Fractalis Test Page</h1>
<p>
This page is for demonstration purpose only.
It showcases the functionality of Fractalis for researchers and
serves as an example on how to use the API for developers.
</p>
<input type="button" onclick="loadData()" value="Prepare data for analysis"/>
<input type="button" onclick="deleteData()" value="Clear analysis cache"/>
<!--<input type="button" onclick="loadState()" value="load state"/>-->
<label for="state-ids">State ID:</label>
<input type="text" id="state-ids" style="width: 20vw;">
<br/>
<br/>
<div class="charts">
<div class="chart chart-1"></div>
<div class="chart chart-2"></div>
<div class="chart chart-3"></div>
<div class="chart chart-4"></div>
<div class="chart copy-1"></div>
<div class="chart copy-2"></div>
<div class="chart copy-3"></div>
<div class="chart chart-5"></div>
<div class="chart chart-6"></div>
<div class="chart chart-7"></div>
<div class="chart chart-8"></div>
</div>
</body>
<script>
......@@ -27,16 +38,18 @@
const fjs = fractal.init({
handler: 'test',
dataSource: 'http://localhost',
fractalisNode: 'http://localhost:5000',
fractalisNode: 'http://localhost',
getAuth () {
return {user: '', passwd: ''}
return {token: ''}
},
options: {
controlPanelPosition: 'right'
}
})
fjs.setSubsets([
[...new Array(100).keys()],
[...new Array(500).keys()].slice(100, 200),
[...new Array(500).keys()].slice(200, 300)
])
function loadData () {
fjs.loadData([
{
......@@ -91,14 +104,18 @@
function setCharts () {
const vms = [
fjs.setChart('correlation-analysis', '.chart-1'),
fjs.setChart('boxplot', '.chart-2'),
fjs.setChart('pca-analysis', '.chart-3'),
fjs.setChart('correlation-analysis', '.chart-4')
fjs.setChart('correlation-analysis', '.chart-2'),
fjs.setChart('correlation-analysis', '.chart-3'),
fjs.setChart('correlation-analysis', '.chart-4'),
fjs.setChart('boxplot', '.chart-5'),
fjs.setChart('boxplot', '.chart-6'),
fjs.setChart('pca-analysis', '.chart-7'),
fjs.setChart('pca-analysis', '.chart-8')
]
vms.forEach((vm, i) => {
fjs.chart2id(vm, id => {
chartStateIDs[i] = id
document.querySelector('.state-ids').value = chartStateIDs.join('+')
document.querySelector('#state-ids').value = chartStateIDs.join('+')
})
})
}
......@@ -109,12 +126,12 @@
</script>
<style>
.containers {
.charts {
}
.chart {
float: left;
width: 48%;
height: 50vh;
float: left;
height: 20vw;
width: 20vw;
}
</style>
import runAnalysis from '../src/utils/run-analysis'
import Vue from 'vue'
import RunAnalysis from '../src/vue/mixins/run-analysis'
import RequestManager from '../src/services/request-manager'
import store from '../src/store/store'
describe('runAnalysis method', () => {
let vm
beforeEach(() => {
vm = new Vue({
mixins: [RunAnalysis]
})
const requestManager = new RequestManager(
{handler: '', dataSource: '', fractalisNode: '', getAuth: () => {}})
store.dispatch('setRequestManager', requestManager)
})
it('returns a promise', () => {
const rv = runAnalysis('', {})
expect(rv instanceof Promise).toBe(true)
})
it('fails if unknown task state', done => {
spyOn(store.getters.requestManager, 'createAnalysis')
.and.returnValue(Promise.resolve({data: {task_id: 123}}))
spyOn(store.getters.requestManager, 'getAnalysisStatus')
.and.returnValue(Promise.resolve({data: {state: 'FOO', result: ''}}))
runAnalysis('', {})
.then(done.fail)
.catch(done)
vm.runAnalysis('', {})
.then(() => {
fail()
})
.catch(() => {
done()
})
})
it('resolves if task state is successful', done => {
......@@ -29,12 +33,12 @@ describe('runAnalysis method', () => {
.and.returnValue(Promise.resolve({data: {task_id: 123}}))
spyOn(store.getters.requestManager, 'getAnalysisStatus')
.and.returnValue(Promise.resolve({data: {state: 'SUCCESS', result: 123}}))
runAnalysis('', {})
vm.runAnalysis('', {})
.then(response => {
expect(response).toBe(123)
done()
})
.catch(done.fail)
.catch(() => fail())
})
it('rejects if task state is unsuccessful', done => {
......@@ -42,9 +46,9 @@ describe('runAnalysis method', () => {
.and.returnValue(Promise.resolve({data: {task_id: 123}}))
spyOn(store.getters.requestManager, 'getAnalysisStatus')
.and.returnValue(Promise.resolve({data: {state: 'FAILURE', result: ''}}))
runAnalysis('', {})
.then(done.fail)
.catch(done)
vm.runAnalysis('', {})
.then(() => fail())
.catch(() => done())
})
it('does wait for task state to switch from SUBMITTED to final state', done => {
......@@ -57,13 +61,13 @@ describe('runAnalysis method', () => {
Promise.resolve({data: {state: 'SUBMITTED', result: ''}}),
Promise.resolve({data: {state: 'SUCCESS', result: 123}})
)
runAnalysis('', {})
vm.runAnalysis('', {})
.then(response => {