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

New tests and trying to make function parameters more consistent (this commit...

New tests and trying to make function parameters more consistent (this commit will probably be really buggy)
parent 18f1a19c
......@@ -6,7 +6,7 @@ import StateManager from './services/state-manager'
class FractalJS {
constructor (handler, dataSource, fractalisNode, getAuth, options) {
const requestManager = new RequestManager({handler, dataSource, fractalisNode, getAuth})
const requestManager = new RequestManager(handler, dataSource, fractalisNode, getAuth)
const chartManager = new ChartManager()
const stateManager = new StateManager()
store.dispatch('setRequestManager', requestManager)
......@@ -28,13 +28,13 @@ class FractalJS {
}
// noinspection JSMethodCanBeStatic
loadData (descriptors) {
return store.getters.requestManager.createData({descriptors})
loadData ({descriptors}) {
return store.getters.requestManager.createData(descriptors)
}
// noinspection JSMethodCanBeStatic
setChart ({chart, selector}) {
return store.getters.chartManager.setChart({chart, selector})
return store.getters.chartManager.setChart(chart, selector)
}
// noinspection JSMethodCanBeStatic
......@@ -43,7 +43,7 @@ class FractalJS {
}
// noinspection JSMethodCanBeStatic
setSubsets (subsets) {
setSubsets ({subsets}) {
store.dispatch('setSubsets', subsets)
}
......@@ -53,13 +53,13 @@ class FractalJS {
}
// noinspection JSMethodCanBeStatic
chart2id (selector, callback) {
chart2id ({selector, callback}) {
return store.getters.stateManager.chart2id(selector, callback)
}
// noinspection JSMethodCanBeStatic
id2chart (selector, id) {
return store.getters.stateManager.id2chart(selector, id)
id2chart ({selector, stateID}) {
return store.getters.stateManager.id2chart(selector, stateID)
}
}
......
......@@ -14,7 +14,7 @@ export default class {
}
}
setChart ({chart, selector}) {
setChart (chart, selector) {
if (!this.AVAILABLE_CHARTS.hasOwnProperty(chart)) {
throw new Error(`Chart '${chart} is not available. Must be one of: ${this.AVAILABLE_CHARTS}`)
}
......@@ -23,8 +23,4 @@ export default class {
vm.$mount(selector)
return vm
}
removeChart ({selector}) {
// TODO: Implement
}
}
......@@ -5,12 +5,12 @@ import store from '../store/store'
* The RequestManager class is responsible for the communication with the back end.
* All atomic API calls are present here and only here.
* This class uses axios for AJAX calls.
*
*{
* Note: You should avoid calling the RequestManager methods directly.
* Instead use the provided helpers available as mixins.
*/
export default class {
constructor ({handler, dataSource, fractalisNode, getAuth}) {
constructor (handler, dataSource, fractalisNode, getAuth) {
this._handler = handler
this._dataSource = dataSource
this._getAuth = getAuth
......@@ -29,7 +29,7 @@ export default class {
* @param descriptors An array of one or more objects that describe the data to be downloaded.
* @returns {AxiosPromise} An ES6 promise.
*/
createData ({descriptors}) {
createData (descriptors) {
return this._axios.post('/data', {
descriptors,
auth: this._getAuth(),
......@@ -43,11 +43,11 @@ export default class {
* @param taskID The data taskID to be reloaded.
* @returns {AxiosPromise} An ES6 promise.
*/
async reloadData ({taskID}) {
const metaData = await this.getMetaData({taskID})
async reloadData (taskID) {
const metaData = await this.getMetaData(taskID)
const descriptors = [metaData.data.meta['descriptor']]
await this.deleteData({taskID})
return this.createData({descriptors})
await this.deleteData(taskID)
return this.createData(descriptors)
}
/**
......@@ -60,10 +60,10 @@ export default class {
/**
* Submits a GET request that will return meta information for the data associated with the given task id.
* @param data task id to get meta information for.
* @param taskID to get meta information for.
* @returns {AxiosPromise} An ES6 promise.
*/
getMetaData ({taskID}) {
getMetaData (taskID) {
return this._axios.get(`/data/meta/${taskID}?wait=1`)
}
......@@ -72,7 +72,7 @@ export default class {
* @param taskID The id of the data to be removed.
* @returns {AxiosPromise} An ES6 promise.
*/
deleteData ({taskID}) {
deleteData (taskID) {
return this._axios.delete(`/data/${taskID}`)
}
......@@ -87,12 +87,12 @@ export default class {
/**
* Submits a POST request that will launch an analysis for the given parameters.
* The returned promise will resolve into the taskID, which can be used to check the status of the task.
* @param task_name The name of the task to submit.
* @param taskName The name of the task to submit.
* @param args The arguments of the task.
* @returns {AxiosPromise} An ES6 promise.
*/
createAnalysis ({task_name, args}) {
return this._axios.post('/analytics', {task_name, args})
createAnalysis (taskName, args) {
return this._axios.post('/analytics', {task_name: taskName, args})
}
/**
......@@ -100,7 +100,7 @@ export default class {
* @param taskID The id of the task.
* @returns {AxiosPromise} An ES6 promise.
*/
getAnalysisStatus ({taskID}) {
getAnalysisStatus (taskID) {
return this._axios.get(`/analytics/${taskID}`)
}
......@@ -109,8 +109,8 @@ export default class {
* @param taskID The id of the task.
* @returns {AxiosPromise} An ES6 promise.
*/
cancelAnalysis ({taskID}) {
store.dispatch('unsetTask', {taskID})
cancelAnalysis (taskID) {
store.dispatch('unsetTask', taskID)
return this._axios.delete(`/analytics/${taskID}`)
}
......@@ -121,9 +121,23 @@ export default class {
getVersion () {
return this._axios.get('/misc/version')
}
/**
* Submits a POST request to save the specified sate object in the Fractalis backend.
* @param state An arbitrary JS object that represents the chart state to save.
* @returns {AxiosPromise} An ES6 promise.
*/
saveState (state) {
return this._axios.post('/state', state)
}
/**
* Submits a POST request to request access for a given state. The response body will be
* empty because this is an async. operation. The result will be made available via GET
* request to the same URL.
* @param stateID The id returned by the POST request to /state.
* @returns {AxiosPromise} An ES6 promise.
*/
requestStateAccess (stateID) {
return this._axios.post(`/state/${stateID}`, {
auth: this._getAuth(),
......@@ -131,6 +145,12 @@ export default class {
server: this._dataSource
})
}
/**
* Submits a GET request to get the state if access has been granted.
* @param stateID The id returned by the POST request to /state.
* @returns {AxiosPromise} An ES6 promise.
*/
getState (stateID) {
return this._axios.get(`/state/${stateID}`)
}
......
......@@ -83,15 +83,15 @@ export default {
* @param taskMessage A message in case the task failed.
*/
setTask: (context, {taskID, taskName, taskState, taskMessage}) => {
context.commit(types.SET_TASK, { taskID, taskName, taskState, taskMessage })
context.commit(types.SET_TASK, {taskID, taskName, taskState, taskMessage})
},
/**
* Commits a tasks mutation that will remove the task for the given taskID.
* @param context The context of the action.
* @param taskID The id of the task to remove.
*/
unsetTask: (context, {taskID}) => {
context.commit(types.UNSET_TASK, {taskID})
unsetTask: (context, taskID) => {
context.commit(types.UNSET_TASK, taskID)
},
/**
* Commits a control panel vm for keeping track of all such instances.
......@@ -99,7 +99,7 @@ export default {
* @param vm The vm of the contr
*/
addControlPanel: (context, vm) => {
context.commit(types.ADD_CONTROL_PANEL, {vm})
context.commit(types.ADD_CONTROL_PANEL, vm)
},
setOptions: (context, options) => {
if (typeof options === 'object') {
......
......@@ -31,10 +31,10 @@ export default {
Vue.set(state.tasks, taskID, {taskID, taskName, taskState, taskMessage})
}
},
[types.UNSET_TASK] (state, {taskID}) {
[types.UNSET_TASK] (state, taskID) {
Vue.delete(state.tasks, taskID)
},
[types.ADD_CONTROL_PANEL] (state, {vm}) {
[types.ADD_CONTROL_PANEL] (state, vm) {
state.controlPanels.push(vm)
},
[types.SET_OPTIONS] (state, options) {
......
......@@ -381,7 +381,7 @@
this.width = width
},
runAnalysisWrapper (args) {
runAnalysis({task_name: 'compute-boxplot', args})
runAnalysis('compute-boxplot', args)
.then(response => {
const results = JSON.parse(response)
results.data = JSON.parse(results.data)
......
......@@ -367,7 +367,7 @@
JSON.stringify(newArgs.categories) !== JSON.stringify(oldArgs.categories) ||
!this.hasSetFilter
if (this.validArgs) {
this.runAnalysisWrapper({init, args: newArgs})
this.runAnalysisWrapper(init, newArgs)
}
this.hasSetFilter = false
}
......@@ -408,9 +408,9 @@
tooltip
},
methods: {
runAnalysisWrapper ({init, args}) {
runAnalysisWrapper (init, args) {
// function made available via requestHandling mixin
runAnalysis({task_name: 'compute-correlation', args})
runAnalysis('compute-correlation', args)
.then(response => {
const results = JSON.parse(response)
results.data = JSON.parse(results.data)
......@@ -439,7 +439,7 @@
ctx.fill()
})
},
resize ({height, width}) {
resize (height, width) {
this.height = height
this.width = width
},
......
......@@ -423,7 +423,7 @@
},
methods: {
computeHeatmap (args) {
runAnalysis({task_name: 'compute-heatmap', args})
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
......@@ -431,7 +431,7 @@
})
},
computeCluster (args) {
runAnalysis({task_name: 'compute-cluster', args})
runAnalysis('compute-cluster', args)
.then(response => {
const results = JSON.parse(response)
deepFreeze(results)
......
......@@ -341,7 +341,7 @@
},
methods: {
runAnalysisWrapper (args) {
runAnalysis({task_name: 'compute-pca', args})
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
......
<template>
<div class="fjs-chart" @mousedown.capture="focusControlPanel">
<slot></slot>
<slot/>
</div>
</template>
......
......@@ -99,7 +99,7 @@
},
featureGetter (taskID) {
return async () => {
const metaData = await store.getters.requestManager.getMetaData({taskID})
const metaData = await store.getters.requestManager.getMetaData(taskID)
return metaData.data.meta['features'] || []
}
},
......@@ -108,10 +108,10 @@
$body.slideToggle(500)
},
reloadData (taskID) {
store.getters.requestManager.reloadData({taskID})
store.getters.requestManager.reloadData(taskID)
},
deleteData (taskID) {
store.getters.requestManager.deleteData({taskID})
store.getters.requestManager.deleteData(taskID)
},
updateFilter (filter, taskID) {
this.featureFilter[taskID] = filter
......
<template>
<div class="fjs-task-view">
<div class="fjs-state-container" v-for="task in incompleteTasks">
<loader class="fjs-loader" :style="{opacity: task.taskState === 'SUBMITTED' ? 1 : 0}"></loader>
<loader class="fjs-loader" :style="{opacity: task.taskState === 'SUBMITTED' ? 1 : 0}"/>
<span class="fjs-submitted" v-if="task.taskState === 'SUBMITTED'">{{ task.taskName }}</span>
<span class="fjs-failed" v-else>{{ task.taskMessage }}</span>
<span class="fjs-cancel-btn" @click="cancelTask(task.taskID)">&#215;</span>
......@@ -33,7 +33,7 @@
},
methods: {
cancelTask (taskID) {
store.getters.requestManager.cancelAnalysis({taskID})
store.getters.requestManager.cancelAnalysis(taskID)
}
}
}
......
......@@ -4,7 +4,7 @@ import store from '../../store/store'
* A helper method to submit an analysis.
* This method returns a promise that resolves into the statistic results of the task once it is finished.
* Please pay special attention to the `args` syntax.
* @param task_name The name to execute. For instance `compute-correlation`
* @param taskName The name to execute. For instance `compute-correlation`
* @param args An object containing all parameters for the task. Strings wrapped in '$' characters are treated as
* data ids. The backend will attempt to replace them with a Python Pandas DataFrame.
* Example args = {method: 'sum', x: '$1234-5678-12345678$'} will replace x with a DataFrame, if available. This
......@@ -12,16 +12,16 @@ 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 ({task_name, args}) {
async function runAnalysis (taskName, args) {
function timeout (ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}
const rv = await store.getters.requestManager.createAnalysis({task_name, args})
const rv = await store.getters.requestManager.createAnalysis(taskName, args)
const taskID = rv.data.task_id
store.dispatch('setTask', {
taskID,
taskName: task_name,
taskName,
taskState: 'SUBMITTED'
})
......@@ -32,19 +32,19 @@ async function runAnalysis ({task_name, args}) {
timeWaited += delay
delay += 100
delay = delay > 3000 ? 3000 : delay
const rv2 = await store.getters.requestManager.getAnalysisStatus({taskID})
const rv2 = await store.getters.requestManager.getAnalysisStatus(taskID)
const taskInfo = rv2.data
if (taskInfo.state === 'SUCCESS') {
store.dispatch('setTask', {
taskID,
taskName: task_name,
taskName,
taskState: taskInfo.state
})
return taskInfo.result
} else if (taskInfo.state === 'FAILURE') {
store.dispatch('setTask', {
taskID,
taskName: task_name,
taskName,
taskState: taskInfo.state,
taskMessage: taskInfo.result
})
......@@ -52,7 +52,7 @@ async function runAnalysis ({task_name, args}) {
} else if (taskInfo.state === 'SUBMITTED') {
store.dispatch('setTask', {
taskID,
taskName: task_name,
taskName,
taskState: taskInfo.state})
} else {
throw new Error(`Analysis Task has unhandled state: ${taskInfo.state}`)
......@@ -61,7 +61,7 @@ async function runAnalysis ({task_name, args}) {
const error = 'Analysis took too long. Stopped listener.'
store.dispatch('setTask', {
taskID,
taskName: task_name,
taskName,
taskState: 'FAILURE',
taskMessage: error
})
......
......@@ -15,12 +15,12 @@ describe('Chart manager', () => {
})
it('fails to set non existing charts', () => {
const f = () => cm.setChart({chart: 'foo', selector: 'placeholder'})
const f = () => cm.setChart('foo', '.placeholder')
expect(f).toThrow()
})
it('sets chart if it exists', () => {
cm.setChart({chart: 'correlation-analysis', selector: 'placeholder'})
cm.setChart('correlation-analysis', '.placeholder')
expect(document.querySelector('.fjs-correlation-analysis')).toBeDefined()
})
})
import runAnalysis from '../src/vue/mixins/run-analysis'
import RequestManager from '../src/services/request-manager'
import store from '../src/store/store'
describe('runAnalysis method', () => {
beforeEach(() => {
const requestManager = new RequestManager(
{handler: '', dataSource: '', fractalisNode: '', getAuth: () => {}})
store.dispatch('setRequestManager', requestManager)
})
it('returns a promise', () => {
const rv = runAnalysis({name: '', args: {}})
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({name: '', args: {}})
.then(done.fail)
.catch(done)
})
it('resolves if task state is successful', 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: 'SUCCESS', result: 123}}))
runAnalysis({name: '', args: {}})
.then(response => {
expect(response).toBe(123)
done()
})
.catch(done.fail)
})
it('rejects if task state is unsuccessful', 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: 'FAILURE', result: ''}}))
runAnalysis({name: '', args: {}})
.then(done.fail)
.catch(done)
})
it('does wait for task state to switch from SUBMITTED to final state', done => {
spyOn(store.getters.requestManager, 'createAnalysis')
.and.returnValue(Promise.resolve({data: {task_id: 123}}))
spyOn(store.getters.requestManager, 'getAnalysisStatus')
.and.returnValues(
Promise.resolve({data: {state: 'SUBMITTED', result: ''}}),
Promise.resolve({data: {state: 'SUBMITTED', result: ''}}),
Promise.resolve({data: {state: 'SUBMITTED', result: ''}}),
Promise.resolve({data: {state: 'SUCCESS', result: 123}})
)
runAnalysis({name: '', args: {}})
.then(response => {
expect(response).toBe(123)
expect(store.getters.requestManager.getAnalysisStatus).toHaveBeenCalledTimes(4)
done()
})
.catch(done.fail)
})
})
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment