Commit 68ff879f authored by charlie-ablett's avatar charlie-ablett

Single step execution

parent e572c282
This diff is collapsed.
......@@ -4,7 +4,7 @@
"description": "Client app for INK",
"scripts": {
"prestart": "",
"start": "parallelshell \"npm run lint:tools\" \"npm run test:watch\" \"npm run open:src\"",
"start": "concurrently -k -r -s first \"npm run lint:tools\" \"npm run test:watch\" \"npm run open:src\"",
"open:src": "babel-node tools/srcServer.js",
"open:dist": "babel-node tools/distServer.js",
"open:dist:staging": "babel-node tools/distStagingServer.js",
......@@ -47,13 +47,13 @@
"react-router": "3.0.0",
"react-select": "1.0.0-rc.3",
"react-smooth-collapse": "1.1.0",
"react-syntax-highlighter": "6.1.1",
"react-timeago": "3.1.3",
"redux": "3.6.0",
"redux-api-middleware": "1.0.2",
"redux-form": "6.8.0",
"redux-thunk": "2.1.0",
"resolve-url-loader": "1.6.0",
"url-loader": "0.5.7",
"yaml-loader": "0.4.0"
},
"devDependencies": {
......@@ -70,6 +70,7 @@
"chai": "3.5.0",
"cheerio": "0.22.0",
"colors": "1.1.2",
"concurrently": "3.5.1",
"cross-env": "3.1.3",
"css-loader": "0.26.0",
"eslint": "3.10.2",
......@@ -91,7 +92,7 @@
"minimist": "1.2.0",
"mocha": "3.1.2",
"node-sass": "3.13.0",
"parallelshell": "2.0.0",
"parallelshell": "3.0.0",
"react-transform-catch-errors": "1.0.2",
"react-transform-hmr": "1.0.4",
"redbox-react": "1.3.3",
......
......@@ -37,6 +37,12 @@ export function resetAlerts(arrayTypes) {
};
}
export function clearAlerts() {
return {
type: types.CLEAR_ALERTS
}
}
export function updateAuthHeaders(response) {
let authToken = response.headers.get('Access-Token');
let tokenType = response.headers.get('Token-Type');
......
......@@ -14,12 +14,10 @@ export function executeRecipe(recipeId, formData, signedIn, authToken, tokenType
if(signedIn === false || signedIn === null || signedIn === undefined) {
return { type: actions.EXECUTE_RECIPE_REQUEST };
}
console.log("execute recipe: 1")
return function(dispatch) {
let theResponse;
dispatch({type: actions.EXECUTE_RECIPE_REQUEST});
console.log("execute recipe: 2")
fetch(`${settings.apiBaseUrl}/api/recipes/${recipeId}/execute`, {
method: 'POST',
headers: {
......@@ -34,23 +32,18 @@ export function executeRecipe(recipeId, formData, signedIn, authToken, tokenType
})
.then(response => {
theResponse = response;
console.log("execute recipe: 3")
return response.json();
})
.then(json => {
checkStatus(theResponse, dispatch, signedIn);
if(_.isUndefined(json.process_chain)) {
console.log("execute recipe: 4a")
let error = new Error(theResponse.statusText);
error.response = json.errors;
throw error;
}
else {
console.log("execute recipe: 4b")
dispatch(setAlert(["Processing started successfully"], AlertTypes.SUCCESS));
console.log("execute recipe: 5")
dispatch(successExecuteRecipe(json));
console.log("execute recipe: 6")
// dispatch(getRecipe(recipeId, signedIn, authToken, tokenType, client, expiry, uid));
}
})
......@@ -77,6 +70,68 @@ export function rejectExecuteRecipe(error) {
};
}
///////////////////////////////////////////////////////////////
///////////////// execute sandbox code ////////////////////////
///////////////////////////////////////////////////////////////
export function executeSandbox(formData, signedIn, authToken, tokenType, client, expiry, uid) {
if(signedIn === false || signedIn === null || signedIn === undefined) {
return { type: actions.EXECUTE_SANDBOX_REQUEST };
}
return function(dispatch) {
let theResponse;
dispatch({type: actions.EXECUTE_SANDBOX_REQUEST});
fetch(`${settings.apiBaseUrl}/api/single_step_executions/create`, {
method: 'POST',
headers: {
'Accept': settings.apiVersionHeader,
'Access-Token': authToken,
'Client': client,
'Token-Type': tokenType,
'Expiry': expiry,
'uid': uid,
},
body: formData
})
.then(response => {
theResponse = response;
return response.json();
})
.then(json => {
checkStatus(theResponse, dispatch, signedIn);
if(_.isUndefined(json.single_step_execution)) {
let error = new Error(theResponse.statusText);
error.response = json.errors;
throw error;
}
else {
dispatch(setAlert(["Processing started successfully"], AlertTypes.SUCCESS));
dispatch(successExecuteSandbox(json));
}
})
.catch(error => {
dispatch(setAlert(error.response || "Error executing code", AlertTypes.ERROR));
dispatch(rejectExecuteSandbox(error));
});
return null;
};
}
export function successExecuteSandbox(json) {
return {
type: actions.EXECUTE_SANDBOX_SUCCESS,
data: json.single_step_execution
};
}
export function rejectExecuteSandbox(error) {
return {
type: actions.EXECUTE_SANDBOX_FAILURE,
error: error.toString()
};
}
///////////////////////////////////////////////////////////////
////////////////// get single recipe //////////////////////////
///////////////////////////////////////////////////////////////
......@@ -313,39 +368,12 @@ export function resetPlaceholders() {
};
}
///////////////////////////////////////////////////////////////
///////////// recipe execution - parameter list ///////////////
///////////////////////////////////////////////////////////////
export function resetParameterList(recipeId, recipeStepId) {
return {
type: actions.RESET_PARAMETER_LIST,
recipeId: recipeId,
recipeStepId: recipeStepId
};
}
export function addToParameterList(recipeId, recipeStepId) {
return {
type: actions.ADD_TO_PARAMETER_LIST,
recipeId: recipeId,
recipeStepId: recipeStepId
};
}
export function removeFromParameterList(recipeId, recipeStepId, index) {
return {
type: actions.REMOVE_FROM_PARAMETER_LIST,
index: index,
recipeId: recipeId,
recipeStepId: recipeStepId
};
}
///////////////////////////////////////////////////////////////
//////////////// expand/collapse file lists ///////////////////
///////////////////////////////////////////////////////////////
////////########## process chain file lists ##########/////////
export function expandChainInputFileList(chainId, recipeId) {
return {
type: actions.EXPAND_CHAIN_INPUT_FILE_LIST,
......@@ -378,6 +406,8 @@ export function collapseChainOutputFileList(chainId, recipeId) {
};
}
//////////######## process step file lists ##########//////////
export function expandStepFileList(stepId, chainId, recipeId) {
return {
type: actions.EXPAND_STEP_FILE_LIST,
......@@ -396,6 +426,36 @@ export function collapseStepFileList(stepId, chainId, recipeId) {
};
}
////////######## single step execution file lists #######////////
export function expandSingleStepExecutionInputFileList(singleStepExecutionId) {
return {
type: actions.EXPAND_SINGLE_STEP_EXECUTION_INPUT_FILE_LIST,
executionId: singleStepExecutionId
};
}
export function collapseSingleStepExecutionInputFileList(singleStepExecutionId) {
return {
type: actions.COLLAPSE_SINGLE_STEP_EXECUTION_INPUT_FILE_LIST,
executionId: singleStepExecutionId
};
}
export function expandSingleStepExecutionOutputFileList(singleStepExecutionId) {
return {
type: actions.EXPAND_SINGLE_STEP_EXECUTION_OUTPUT_FILE_LIST,
executionId: singleStepExecutionId
};
}
export function collapseSingleStepExecutionOutputFileList(singleStepExecutionId) {
return {
type: actions.COLLAPSE_SINGLE_STEP_EXECUTION_OUTPUT_FILE_LIST,
executionId: singleStepExecutionId
};
}
///////////////////////////////////////////////////////////////
////////////////// un/favourite recipe ////////////////////////
///////////////////////////////////////////////////////////////
......
......@@ -63,3 +63,42 @@ export function downloadLogFile(filePath, processStepId, appState, dispatch) {
const downloadPath = `${settings.apiBaseUrl}/api/process_steps/${processStepId}/download_process_log`;
downloadFile(downloadPath, filePath, appState, dispatch);
}
// single step execution from sandbox
export function downloadCodeFile(filePath, executionId, appState, dispatch) {
// /api/single_step_executions/:id/download_code_file
const downloadPath = `${settings.apiBaseUrl}/api/single_step_executions/${executionId}/download_code`;
downloadFile(
downloadPath, filePath, appState, dispatch);
}
export function downloadSingleStepExecutionLogFile(filePath, executionId, appState, dispatch) {
// /api/process_steps/:id/download_output_file
const downloadPath = `${settings.apiBaseUrl}/api/single_step_executions/${executionId}/download_process_log`;
downloadFile(downloadPath, filePath, appState, dispatch);
}
export function downloadSingleStepExecutionOutputFile(filePath, executionId, appState, dispatch) {
// /api/single_step_executions/:id/download_output_file
const downloadPath = `${settings.apiBaseUrl}/api/single_step_executions/${executionId}/download_output_file?relative_path=${filePath}`;
downloadFile(downloadPath, filePath, appState, dispatch);
}
export function downloadSingleStepExecutionOutputZip(executionId, appState, dispatch) {
// /api/single_step_executions/:id/download_output_zip
const downloadPath = `${settings.apiBaseUrl}/api/single_step_executions/${executionId}/download_output_zip`;
downloadFile(downloadPath, "output.zip", appState, dispatch);
}
export function downloadSingleStepExecutionInputZip(executionId, appState, dispatch) {
// /api/single_step_executions/:id/download_input_zip
const downloadPath = `${settings.apiBaseUrl}/api/single_step_executions/${executionId}/download_input_zip`;
downloadFile(downloadPath, "input.zip", appState, dispatch);
}
export function downloadSingleStepExecutionInputFile(filePath, executionId, appState, dispatch) {
// /api/single_step_executions/:id/download_input_file
const downloadPath = `${settings.apiBaseUrl}/api/single_step_executions/${executionId}/download_input_file?relative_path=${filePath}`;
downloadFile(downloadPath, filePath, appState, dispatch);
}
import React from 'react';
import React, {PropTypes} from 'react';
import { Field, FieldArray, reduxForm } from 'redux-form';
import validate from './validate';
import { connect } from 'react-redux';
......@@ -10,7 +10,7 @@ export class ParameterArrayForm extends React.Component {
errorIcon (touched, error) {
if(touched && error) {
return(<span className='fa fa-exclamation-triangle red' title={error}></span>);
return(<span className="fa fa-exclamation-triangle red" title={error}/>);
}
return null;
}
......@@ -89,6 +89,16 @@ export class ParameterArrayForm extends React.Component {
}
}
ParameterArrayForm.propTypes = {
appState: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
handleSubmit: PropTypes.func.isRequired,
pristine: PropTypes.bool.isRequired,
submitting: PropTypes.bool.isRequired,
invalid: PropTypes.bool.isRequired,
initialValues: PropTypes.object.isRequired,
reset: PropTypes.func.isRequired
};
function mapStateToProps(state, props) {
return {
......
......@@ -17,15 +17,8 @@ export class ParameterFormPosition extends React.Component {
handlePopulatePreset = (e, preset) => {
e.preventDefault();
const { appState, dispatch, recipeStep } = this.props;
console.log("POPULATING", preset)
}
// componentDidMount() {
// const { dispatch, appState, recipeStep } = this.props;
// dispatch(getPresetsForRecipeStep(recipeStep, appState));
// }
getPresets = (input) => {
const { dispatch, appState, recipeStep } = this.props;
dispatch(getPresetsForRecipeStep(recipeStep, appState));
......@@ -38,7 +31,7 @@ export class ParameterFormPosition extends React.Component {
}
handleSubmit = (values) => {
console.log("handleSubmit VALUES", values)
// console.log("handleSubmit VALUES", values)
}
onChange = (value) => {
......@@ -108,7 +101,7 @@ export class ParameterFormPosition extends React.Component {
<div className="tab-container">
<div className="tab-item inactive-parameter-tab" onClick={e => this.handleTabSwitch(e, viewConstants.SHOW_PARAMETER_FORM)}>Parameters</div>
<div className="tab-item active-parameter-tab">Presets</div>
<div className="tab-item parameter-tab-spacer"></div>
<div className="tab-item parameter-tab-spacer"/>
</div>
);
}
......@@ -117,7 +110,7 @@ export class ParameterFormPosition extends React.Component {
<div className="tab-container">
<div className="tab-item active-parameter-tab">Parameters</div>
<div className="tab-item inactive-parameter-tab" onClick={e => this.handleTabSwitch(e, viewConstants.SHOW_PRESET_VIEW)}>Presets</div>
<div className="tab-item parameter-tab-spacer"></div>
<div className="tab-item parameter-tab-spacer"/>
</div>
);
}
......
import { Field, FieldArray, reduxForm } from 'redux-form';
import { connect } from 'react-redux';
import React from 'react';
import React, {PropTypes} from 'react';
export class SavePresetForm extends React.Component {
......@@ -68,6 +68,18 @@ export class SavePresetForm extends React.Component {
}
}
SavePresetForm.propTypes = {
appState: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
processStep: PropTypes.object.isRequired,
handleSubmit: PropTypes.func,
pristine: PropTypes.bool,
submitting: PropTypes.bool,
invalid: PropTypes.bool,
initialValues: PropTypes.object,
reset: PropTypes.func
};
function mapStateToProps(state, props) {
return {
appState: state.appState,
......
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators, compose } from 'redux';
import * as actions from '../../actions/recipeActions.js';
import _ from 'lodash';
import Dropzone from 'react-dropzone';
import { Field, reduxForm, Form, formValueSelector } from 'redux-form';
import validateSandbox from './ValidateSandbox';
import SandboxParameterForm from './SandboxParameterForm';
class CodeForm extends Component {
onSubmit(data) {
const { dispatch, appState } = this.props;
let formData = new FormData();
let sandboxParams = {};
sandboxParams.step_class_name = data.step_class_name;
sandboxParams.description = data.description;
sandboxParams.execution_parameters = this.assembleParameters(this.props.state);
formData.append("single_step_execution", JSON.stringify(sandboxParams));
_.map(data.input_files, function(file) {
formData.append('input_files[]', file);
});
formData.append('code', data.code);
const { signedIn, authToken, tokenType, client, expiry, uid } = appState.session;
dispatch(actions.executeSandbox(formData, signedIn, authToken, tokenType, client, expiry, uid));
}
assembleParameters(state) {
let newSet = {};
const selector = formValueSelector(`sandboxParameterForm`);
let params = selector(state, 'parameters');
_.forEach(params, function(paramSet) {
newSet[paramSet.key] = paramSet.value;
});
return newSet;
}
listExpandCollapseToggle(singleStepExecution) {
return(
<a href="#" onClick={e => this.handleToggle(e, singleStepExecution.file_list_collapse, singleStepExecution.id, singleStepExecution.process_chain_id, this.props.recipeId)}>
<span className={this.listIcon(singleStepExecution.file_list_collapse)}/>
&nbsp;Files ({_.size(singleStepExecution.output_file_manifest)})
</a>
);
}
listIcon(collapsed) {
if(collapsed == true || _.isNil(collapsed)) {
return "fa fa-caret-right";
} else {
return "fa fa-caret-down";
}
}
successClass(execution_errors, successful) {
if(successful === true) {
return("success success-background");
}
else if(!_.isNil(execution_errors) && !_.isEmpty(execution_errors)) {
return("fail fail-background");
}
return("");
}
renderIcon(singleStepExecution) {
if(singleStepExecution.in_progress === true) {
return(<span className="fa fa-gear fa-spin fa-2x fa-fw step-processing-centered" />);
}
else if(_.isNil(singleStepExecution.started_at)) {
return(<span className="small-info-text fa fa-question"/>);
}
else if(singleStepExecution.successful === true) {
return(<span className="fa fa-check"/>);
}
else if(!_.isNil(singleStepExecution.execution_errors) && !_.isEmpty(singleStepExecution.execution_errors)) {
return(<span className="fa fa-times"/>);
}
}
executionButton(inProgress) {
if(inProgress) {
return(
<button type="submit" className="sandbox-go-button" disabled>
Starting... <span className="fa fa-gear fa-spin fa-fw" />
</button>
);
}
else {
return(
<button type="submit" className="sandbox-go-button">
GO!
</button>
);
}
}
lookBelowMessage(executions) {
if(executions.length > 0) {
return(
<span>Now look below! <span className="fa fa-hand-o-down" /></span>
);
}
}
render() {
const { appState, handleSubmit, load, pristine, reset, submitting } = this.props;
return(
<Form onSubmit={handleSubmit(this.onSubmit.bind(this))}>
<h2>1. Write some code <span className="fa fa-diamond"/></h2>
<div className="sandbox-left">
<div>
<Field
name="step_class_name"
component={renderField}
type="text"
placeholder="Name of Class (must match class name below)"
label="Step Class Name (must match below)" />
</div>
</div>
<div>
<Field
name="description"
component={renderField}
type="text"
placeholder="Describe your step"
label="Description" />
</div>
<div>
<label>Step Code (ruby) <span className="fa fa-diamond"/></label>
<div>
<Field className="sandbox-code-input" rows="30" cols="80" name="code" component="textarea" />
</div>
</div>
<h2>2. Choose some Input Files <span className="fa fa-file-o"/></h2>
<div className="sandbox-component-container">
<div>
<Field
name="input_files"
component={renderDropzoneInput}
/>
</div>
</div>
<div>
<h2>3. Specify any parameters <span className="fa fa-key"/></h2>
<div className="sandbox-component-container">
<SandboxParameterForm onSubmit={this.handleSubmit} />
</div>
</div>
<div>
<h2>4. Ready... get set... <span className="fa fa-flag-checkered"/></h2>
<div className="sandbox-component-container padding-top-1rem">
{ this.executionButton(appState.sandboxPlaceholderCount >= 1) }
{ this.lookBelowMessage(appState.singleStepExecutions) }
</div>
</div>
</Form>
);
}
}
const renderUploadFileList = (files) => {
if(files && Array.isArray(files)) {
return(
<ul className="dropzone-file-list">
{ files.map((file, i) =>
<li key={i} className="dropzone-file-list-item">
<span className="fa fa-file-o"/> {file.name}
</li>
)}
</ul>
);
} else {
return(<div className="left-indent small-info"><span className="fa fa-hand-o-left"/> Please select some</div>);
}
};
const renderDropzoneInput = (field) => {
const files = field.input.value;
return (
<div className="dropzone-container">
<div className="dropzone-widget">
<Dropzone
name={field.name}
onDrop={( filesToUpload, e ) => field.input.onChange(filesToUpload)}
>
<div className="dropzone">Drop files here, or click to select files.</div>
<div className="dropzone-icon-container">
<span className="dropzone-icon fa fa-files-o"/>
</div>
</Dropzone>
{field.meta.touched &&
field.meta.error &&
<span className="error">{field.meta.error}</span>}
</div>
<div className="dropzone-file-list">
<div className="dropzone-file-list-title left-indent"><span className="fa fa-files-o"/> Files to upload ({files.length})</div>
{ renderUploadFileList(files) }
</div>
</div>
);
};
const handleCodeChange = (e) => {
console.log("value", e.target.value);
};
const renderField = ({ input, label, type, meta: { touched, error, warning } }) => (
<div>
<label>{label}</label>
<div>
<input className="sandbox-input-wide" {...input} placeholder={label} type={type}/>
{touched && ((error && <span>{error}</span>) || (warning && <span>{warning}</span>))}
</div>
</div>
);
CodeForm.propTypes = {
handleSubmit: PropTypes.func.isRequired,
reset: PropTypes.func.isRequired,
dispatch: PropTypes.func.isRequired,
appState: PropTypes.object.isRequired
};
function mapStateToProps(state) {
return {
appState: state.appState,
state: state
};
}
const defaultData = {
step_class_name: "SampleStep",
description: "A sample step class",
execution_parameters: {blargh: "haha"},
code:
`class SampleStep < InkStep::ConversionStep
def perform_step
# put some logic here
success!
end
def version
"1.0"
end
def description
"A sample step that doesn't do anything"
end
def self.human_readable_name
"Sample step"
end
end`
};
export default compose(
connect(mapStateToProps),
reduxForm(
{form: "sandbox",
initialValues: defaultData
}, validateSandbox
)
)(CodeForm);
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import _ from "lodash";
import SmoothCollapse from 'react-smooth-collapse';
import TimeAgo from 'react-timeago';
import ExecutionFileList from './ExecutionFileList';
import * as actions from '../../actions/authenticationActions';
import * as recipeActions from '../../actions/recipeActions';
import { downloadSingleStepExecutionLogFile, downloadCodeFile } from '../../businessLogic/fileLogic';
export class ExecutionDetail extends Component {
handleLogFileDownload = (e, fileName) => {
e.preventDefault();
const { appState, dispatch, singleStepExecution } = this.props;
dispatch(downloadSingleStepExecutionLogFile(fileName, singleStepExecution.id, appState, dispatch));
}
handleCodeFileDownload = (e, fileName) => {
e.preventDefault();
const { appState, dispatch, singleStepExecution } = this.props;
dispatch(downloadCodeFile(fileName, singleStepExecution.id, appState, dispatch));
}
renderFinishedTitle() {
let { singleStepExecution } = this.props;
return(<span>Finished processing <TimeAgo date={singleStepExecution.finished_at} /> </span>);
}
renderInProgressTitle() {