Commit 56347288 authored by hh1966's avatar hh1966
Browse files

Add research plan table schemas

parent 60c35dbf
Pipeline #52172 failed with stage
......@@ -91,6 +91,41 @@ module Chemotion
}
end
namespace :table_schemas do
desc "Return serialized table schemas of current user"
get do
{ table_schemas: ResearchPlanTableSchema.where( creator: current_user)}
end
desc "Save table schema"
params do
requires :name, type: String
requires :value, type: Hash
end
post do
attributes = {
name: params[:name],
value: params[:value]
}
table_schema = ResearchPlanTableSchema.new attributes
table_schema.creator = current_user
table_schema.save!
table_schema
end
desc "Delete table schema"
route_param :id do
before do
error!('401 Unauthorized', 401) unless TableSchemaPolicy.new(current_user, ResearchPlanTableSchema.find(params[:id])).destroy?
end
delete do
ResearchPlanTableSchema.find(params[:id]).destroy
end
end
end
desc "Create a research plan"
params do
requires :name, type: String, desc: "Research plan name"
......
......@@ -120,4 +120,50 @@ export default class ResearchPlansFetcher {
});
return promise;
}
static fetchTableSchemas() {
return fetch('/api/v1/research_plans/table_schemas/', {
credentials: 'same-origin',
method: 'get',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json'
}
}).then(response => {
return response.json()
}).catch((errorMessage) => {
console.log(errorMessage)
})
}
static createTableSchema(name, value) {
return fetch('/api/v1/research_plans/table_schemas/', {
credentials: 'same-origin',
method: 'post',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({ name, value })
}).then(response => {
return response.json()
}).catch((errorMessage) => {
console.log(errorMessage)
})
}
static deleteTableSchema(id) {
return fetch('/api/v1/research_plans/table_schemas/' + id, {
credentials: 'same-origin',
method: 'delete',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json'
}
}).then(response => {
return response.json()
}).catch((errorMessage) => {
console.log(errorMessage)
})
}
}
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { range } from 'lodash';
import { Row, Col, Button } from 'react-bootstrap'
import ReactDataGrid from 'react-data-grid';
import { Menu } from "react-data-grid-addons"
import ResearchPlanDetailsFieldTableContextMenu from './ResearchPlanDetailsFieldTableContextMenu'
import ResearchPlanDetailsFieldTableToolbar from './ResearchPlanDetailsFieldTableToolbar'
import ResearchPlanDetailsFieldTableColumnNameModal from './ResearchPlanDetailsFieldTableColumnNameModal'
import ResearchPlanDetailsFieldTableSchemasModal from './ResearchPlanDetailsFieldTableSchemasModal'
import ResearchPlansFetcher from '../fetchers/ResearchPlansFetcher'
const { ContextMenuTrigger } = Menu
......@@ -20,8 +22,13 @@ export default class ResearchPlanDetailsFieldTable extends Component {
super(props);
this.state = {
update: this.props.update,
showModal: '',
idx: null,
columnNameModal: {
show: false,
idx: null
},
schemaModal: {
show: false
},
selection: {}
}
......@@ -78,27 +85,35 @@ export default class ResearchPlanDetailsFieldTable extends Component {
this.setState({ selected: {} })
}
handleColumnNameModalOpen(showModal, idx) {
handleColumnNameModalShow(action, idx) {
this.setState({
showModal: showModal,
idx: idx
columnNameModal: {
show: true,
action: action,
idx: idx
}
})
}
handleColumnNameModalSubmit(columnName) {
const { showModal, idx } = this.state
const { action, idx } = this.state.columnNameModal
if (columnName) {
if (showModal == 'insert') {
this.handleColumnInsert(idx, columnName)
} else if (showModal == 'rename') {
this.handleColumnRename(idx, columnName)
}
if (action == 'insert') {
this.handleColumnInsert(idx, columnName)
} else if (action == 'rename') {
this.handleColumnRename(idx, columnName)
}
this.handleColumnNameModalHide()
}
handleColumnNameModalHide() {
this.setState({
showModal: '',
idx: null
columnNameModal: {
show: false,
action: null,
idx: null
}
})
}
......@@ -206,6 +221,44 @@ export default class ResearchPlanDetailsFieldTable extends Component {
event.clipboardData.setData('text/plain', text);
}
handleSchemaModalShow() {
ResearchPlansFetcher.fetchTableSchemas().then(json => {
this.setState({
schemaModal: {
show: true,
schemas: json['table_schemas']
}
})
})
}
handleSchemasModalSubmit(schemaName) {
ResearchPlansFetcher.createTableSchema(schemaName, this.props.field.value).then(() => {
this.handleSchemaModalShow()
})
}
handleSchemasModalHide() {
this.setState({
schemaModal: {
show: false
}
})
}
handleSchemasModalUse(schema) {
const { field, onChange } = this.props
onChange(schema.value, field.id)
this.handleSchemasModalHide()
}
handleSchemasModalDelete(schema) {
ResearchPlansFetcher.deleteTableSchema(schema.id).then(() => {
this.handleSchemaModalShow()
})
}
rowGetter(idx) {
return this.props.field.value.rows[idx]
}
......@@ -213,47 +266,65 @@ export default class ResearchPlanDetailsFieldTable extends Component {
render() {
const { field } = this.props
const { rows, columns } = field.value
const { showModal } = this.state
const { columnNameModal, schemaModal } = this.state
const editorPortalTarget = document.getElementsByClassName('react-grid-Viewport')[0]
return (
<div>
<ReactDataGrid
columns={columns}
rowGetter={this.rowGetter.bind(this)}
rowsCount={rows.length}
minHeight={272}
onGridRowsUpdated={event => this.handleEdit(event)}
enableCellSelect={true}
editorPortalTarget={editorPortalTarget}
cellRangeSelection={{
onComplete: this.handleRangeSelection.bind(this),
}}
onCellSelected={this.handleCellSelected.bind(this)}
onCellDeSelected={this.handleCellDeSelected.bind(this)}
onColumnResize={this.handleColumnResize.bind(this)}
toolbar={
<ResearchPlanDetailsFieldTableToolbar
/>
}
contextMenu={
<ResearchPlanDetailsFieldTableContextMenu
onColumnInsertLeft={(event, { idx }) => this.handleColumnNameModalOpen('insert', idx)}
onColumnInsertRight={(event, { idx }) => this.handleColumnNameModalOpen('insert', idx + 1)}
onColumnRename={(event, { idx }) => this.handleColumnNameModalOpen('rename', idx)}
onColumnDelete={(event, { idx }) => this.handleColumnDelete(idx)}
onRowInsertAbove={(event, { rowIdx }) => this.handleRowInsert(rowIdx)}
onRowInsertBelow={(event, { rowIdx }) => this.handleRowInsert(rowIdx + 1)}
onRowDelete={(event, { rowIdx }) => this.handleRowDelete(rowIdx)}
/>
}
RowsContainer={ContextMenuTrigger}
/>
<div className="research-plan-table-grid">
<ReactDataGrid
columns={columns}
rowGetter={this.rowGetter.bind(this)}
rowsCount={rows.length}
minHeight={272}
onGridRowsUpdated={event => this.handleEdit(event)}
enableCellSelect={true}
editorPortalTarget={editorPortalTarget}
// cellRangeSelection={{
// onComplete: this.handleRangeSelection.bind(this),
// }}
onCellSelected={this.handleCellSelected.bind(this)}
onCellDeSelected={this.handleCellDeSelected.bind(this)}
onColumnResize={this.handleColumnResize.bind(this)}
contextMenu={
<ResearchPlanDetailsFieldTableContextMenu
onColumnInsertLeft={(event, { idx }) => this.handleColumnNameModalShow('insert', idx)}
onColumnInsertRight={(event, { idx }) => this.handleColumnNameModalShow('insert', idx + 1)}
onColumnRename={(event, { idx }) => this.handleColumnNameModalShow('rename', idx)}
onColumnDelete={(event, { idx }) => this.handleColumnDelete(idx)}
onRowInsertAbove={(event, { rowIdx }) => this.handleRowInsert(rowIdx)}
onRowInsertBelow={(event, { rowIdx }) => this.handleRowInsert(rowIdx + 1)}
onRowDelete={(event, { rowIdx }) => this.handleRowDelete(rowIdx)}
/>
}
RowsContainer={ContextMenuTrigger}
/>
</div>
<div className="research-plan-table-toolbar">
<Row>
<Col lg={3}>
<Button bsSize="xsmall" onClick={this.handleSchemaModalShow.bind(this)}>
Table schemas
</Button>
</Col>
<Col lg={3} lgOffset={6}>
<Button bsSize="xsmall">
Export as Excel
</Button>
</Col>
</Row>
</div>
<ResearchPlanDetailsFieldTableColumnNameModal
showModal={showModal}
columns={columns}
onSubmit={this.handleColumnNameModalSubmit.bind(this)}/>
modal={columnNameModal}
onSubmit={this.handleColumnNameModalSubmit.bind(this)}
onHide={this.handleColumnNameModalHide.bind(this)}
columns={columns} />
<ResearchPlanDetailsFieldTableSchemasModal
modal={schemaModal}
onSubmit={this.handleSchemasModalSubmit.bind(this)}
onHide={this.handleSchemasModalHide.bind(this)}
onUse={this.handleSchemasModalUse.bind(this)}
onDelete={this.handleSchemasModalDelete.bind(this)} />
</div>
)
}
......
......@@ -13,51 +13,42 @@ class ResearchPlanDetailsFieldTableColumnNameModal extends Component {
columnNameValue: columnName ? columnName : '',
columnNameError: ''
}
this.handleColumnNameChange = this.handleColumnNameChange.bind(this)
this.handleSubmit = this.handleSubmit.bind(this)
}
handleColumnNameChange(event) {
this.setState({ columnNameValue: event.target.value });
}
handleSubmit(submit) {
handleSubmit() {
const { columns, onSubmit } = this.props
const { columnNameValue } = this.state
const keys = columns.map(column => { return column.key })
if (submit) {
const keys = columns.map(column => { return column.key })
if (!columnNameValue) {
this.setState({ columnNameError: 'Please give a column name.' })
} else if (keys.indexOf(columnNameValue) > -1) {
this.setState({ columnNameError: 'A column with this title already exists.' })
} else {
this.setState({ columnNameError: '', columnNameValue: '' })
onSubmit(columnNameValue)
}
if (!columnNameValue) {
this.setState({ columnNameError: 'Please give a column name.' })
} else if (keys.indexOf(columnNameValue) > -1) {
this.setState({ columnNameError: 'A column with this title already exists.' })
} else {
this.setState({ columnNameError: '', columnNameValue: '' })
onSubmit()
onSubmit(columnNameValue)
}
}
render() {
const { showModal } = this.props
const { modal, onHide } = this.props
const { columnNameValue, columnNameError } = this.state
let show, title
if (showModal == 'insert') {
show = true
let title
if (modal.action == 'insert') {
title = 'Insert column'
} else if (showModal == 'rename') {
show = true
} else if (modal.action == 'rename') {
title = 'Rename column'
}
let validationState = columnNameError ? 'error' : null
return (
<Modal animation show={show}>
<Modal animation show={modal.show} onHide={onHide}>
<Modal.Header closeButton>
<Modal.Title>
{title}
......@@ -70,17 +61,17 @@ class ResearchPlanDetailsFieldTableColumnNameModal extends Component {
<FormControl
type="text"
value={columnNameValue}
onChange={this.handleColumnNameChange}
onChange={this.handleColumnNameChange.bind(this)}
/>
<HelpBlock>{columnNameError}</HelpBlock>
</FormGroup>
</div>
<div>
<ButtonToolbar>
<Button bsStyle="warning" onClick={event => this.handleSubmit(false)}>
<Button bsStyle="warning" onClick={onHide}>
Cancel
</Button>
<Button bsStyle="primary" onClick={event => this.handleSubmit(true)}>
<Button bsStyle="primary" onClick={this.handleSubmit.bind(this)}>
{title}
</Button>
</ButtonToolbar>
......@@ -92,9 +83,10 @@ class ResearchPlanDetailsFieldTableColumnNameModal extends Component {
}
ResearchPlanDetailsFieldTableColumnNameModal.propTypes = {
showModal: PropTypes.string,
modal: PropTypes.object,
columnName: PropTypes.string,
onSubmit: PropTypes.func,
onHide: PropTypes.func,
columns: PropTypes.array
}
......
import React, { Component} from "react"
import PropTypes from 'prop-types'
import { Table, Modal, ButtonToolbar, Button, FormGroup, ControlLabel, FormControl, HelpBlock, InputGroup } from 'react-bootstrap'
class ResearchPlanDetailsFieldTableSchemasModal extends Component {
constructor(props) {
super(props);
this.state = {
schemaNameValue: '',
schemaNameError: ''
}
}
handleSchemaNameChange(event) {
this.setState({ schemaNameValue: event.target.value });
}
handleSubmit() {
const { onSubmit } = this.props
const { schemaNameValue } = this.state
if (!schemaNameValue) {
this.setState({ schemaNameError: 'Please give a schema name.' })
} else {
this.setState({ schemaNameError: '', schemaNameValue: '' })
onSubmit(schemaNameValue)
}
}
render() {
const { modal, onHide, onUse, onDelete } = this.props
const { schemaNameValue, schemaNameError } = this.state
let schemaTable = null
if (modal.schemas) {
schemaTable = modal.schemas.map((schema, index) => {
return (
<tr key={index}>
<td>{schema.name}</td>
<td>
{schema.value.columns.map(column => {
return column.name
}).join(', ')}
</td>
<td>
{schema.value.rows.length}
</td>
<td>
<Button bsStyle="danger" bsSize="xsmall" onClick={() => onDelete(schema)}>
Delete
</Button>
<Button bsStyle="warning" bsSize="xsmall" onClick={() => onUse(schema)}>
Use
</Button>
</td>
</tr>
)
})
}
return (
<Modal animation show={modal.show} onHide={onHide}>
<Modal.Header closeButton>
<Modal.Title>
Table schemas
</Modal.Title>
</Modal.Header>
<Modal.Body >
<div className="research-plan-table-schema-modal-create">
<FormGroup validationState={schemaNameError ? 'error' : null}>
<ControlLabel>Save current schema</ControlLabel>
<InputGroup>
<FormControl
type="text"
value={schemaNameValue}
onChange={this.handleSchemaNameChange.bind(this)}
/>
<InputGroup.Button>
<Button bsStyle="success" onClick={this.handleSubmit.bind(this)}>
Save
</Button>
</InputGroup.Button>
</InputGroup>
<HelpBlock>{schemaNameError}</HelpBlock>
</FormGroup>
</div>
<div className="research-plan-table-schema-modal-table">
<h4>Stored schemas</h4>
<Table>
<thead>
<tr>
<th style={{width: '20%'}}>Name</th>
<th style={{width: '40%'}}>Columns</th>
<th style={{width: '20%'}}># Rows</th>
<th style={{width: '20%'}}></th>
</tr>
</thead>
<tbody>
{schemaTable}
</tbody>
</Table>
</div>
<div>
<Button bsStyle="default" onClick={onHide}>
Close
</Button>
</div>
</Modal.Body>
</Modal>
)
}
}
ResearchPlanDetailsFieldTableSchemasModal.propTypes = {
modal: PropTypes.object,
onSubmit: PropTypes.func,
onHide: PropTypes.func,
onUse: PropTypes.func,
onDelete: PropTypes.func
}
export default ResearchPlanDetailsFieldTableSchemasModal
import React, { Component} from "react"
import PropTypes from 'prop-types'
import NotificationActions from '../actions/NotificationActions';
class ResearchPlanDetailsFieldTableToolbar extends Component {
constructor(props) {
super(props);
this.state = {
}
}
render() {
// const { } = this.props
// const { } = this.state