diff --git a/openbis_ng_ui/jest.config.js b/openbis_ng_ui/jest.config.js index 8e12627b1a2a51ab0ce042e19ecea29c63ca6804..f493ac2b3bb36fefcd7228018bf9b8673c7ed0ff 100644 --- a/openbis_ng_ui/jest.config.js +++ b/openbis_ng_ui/jest.config.js @@ -28,6 +28,7 @@ module.exports = { moment: '<rootDir>/srcV3/lib/moment/js/moment.js', stjs: '<rootDir>/srcV3/lib/stjs/js/stjs.js', underscore: '<rootDir>/srcV3/lib/underscore/js/underscore.js', + '\\.css$': '<rootDir>/srcTest/js/mockStyles.js', 'openbis.js': '<rootDir>/srcTest/js/services/openbis.js', '^@src/(.*)$': '<rootDir>/src/$1', '^@srcTest/(.*)$': '<rootDir>/srcTest/$1', diff --git a/openbis_ng_ui/package.json b/openbis_ng_ui/package.json index 41eb6a858624e65777bc677bf9dbeae70d4140e0..91916e3b1acb7a67703a0fa7cd6a9f04b04bbe06 100644 --- a/openbis_ng_ui/package.json +++ b/openbis_ng_ui/package.json @@ -12,12 +12,15 @@ "install": "^0.13.0", "npm": "^6.14.8", "path-to-regexp": "^6.1.0", + "prism-themes": "^1.5.0", + "prismjs": "^1.22.0", "prop-types": "^15.7.2", "re-resizable": "^6.5.4", "react": "^16.13.1", "react-beautiful-dnd": "^13.0.0", "react-dom": "^16.13.1", "react-redux": "^7.2.1", + "react-simple-code-editor": "^0.11.0", "redux": "^4.0.5", "redux-saga": "^1.1.3", "reselect": "^4.0.0", diff --git a/openbis_ng_ui/src/js/common/consts/objectType.js b/openbis_ng_ui/src/js/common/consts/objectType.js index 130269b01f323e3233800e38c7cbca47aa0b44f4..08e9d6b2fb863171f4e37efdd59a04be241f24cd 100644 --- a/openbis_ng_ui/src/js/common/consts/objectType.js +++ b/openbis_ng_ui/src/js/common/consts/objectType.js @@ -5,6 +5,8 @@ const NEW_MATERIAL_TYPE = 'newMaterialType' const NEW_VOCABULARY_TYPE = 'newVocabularyType' const NEW_USER = 'newUser' const NEW_USER_GROUP = 'newUserGroup' +const NEW_DYNAMIC_PROPERTY_PLUGIN = 'newDynamicPropertyPlugin' +const NEW_ENTITY_VALIDATION_PLUGIN = 'newEntityValidationPlugin' const OBJECT_TYPE = 'objectType' const COLLECTION_TYPE = 'collectionType' @@ -13,6 +15,8 @@ const MATERIAL_TYPE = 'materialType' const VOCABULARY_TYPE = 'vocabularyType' const USER = 'user' const USER_GROUP = 'userGroup' +const DYNAMIC_PROPERTY_PLUGIN = 'dynamicPropertyPlugin' +const ENTITY_VALIDATION_PLUGIN = 'entityValidationPlugin' const SEARCH = 'search' @@ -24,6 +28,8 @@ export default { NEW_VOCABULARY_TYPE, NEW_USER, NEW_USER_GROUP, + NEW_DYNAMIC_PROPERTY_PLUGIN, + NEW_ENTITY_VALIDATION_PLUGIN, OBJECT_TYPE, COLLECTION_TYPE, DATA_SET_TYPE, @@ -31,5 +37,7 @@ export default { VOCABULARY_TYPE, USER, USER_GROUP, + DYNAMIC_PROPERTY_PLUGIN, + ENTITY_VALIDATION_PLUGIN, SEARCH } diff --git a/openbis_ng_ui/src/js/common/consts/pages.js b/openbis_ng_ui/src/js/common/consts/pages.js index 11578ee6d87219f62aed5e32c7a38305536ed0fc..07c09cbe73356c6d7c8cc0886c70f9b492331f84 100644 --- a/openbis_ng_ui/src/js/common/consts/pages.js +++ b/openbis_ng_ui/src/js/common/consts/pages.js @@ -1,9 +1,11 @@ const LOGIN = 'login' const TYPES = 'types' const USERS = 'users' +const TOOLS = 'tools' export default { LOGIN, TYPES, - USERS + USERS, + TOOLS } diff --git a/openbis_ng_ui/src/js/common/consts/routes.js b/openbis_ng_ui/src/js/common/consts/routes.js index 749aad7db45613efbe6f119329c5709bf5c7728f..cf8d30307b3202dca8c6643de4721e1fdb81b0fc 100644 --- a/openbis_ng_ui/src/js/common/consts/routes.js +++ b/openbis_ng_ui/src/js/common/consts/routes.js @@ -3,306 +3,160 @@ import { compile, match } from 'path-to-regexp' import pages from '@src/js/common/consts/pages.js' import objectTypes from '@src/js/common/consts/objectType.js' -function doFormat(actualParams, pattern, requiredParams) { - if (_.isMatch(actualParams, requiredParams)) { - let toPath = compile(pattern, { encode: encodeURIComponent }) - return { - path: toPath(actualParams), - match: Object.keys(requiredParams).length +class Route { + constructor(pattern, params) { + this.pattern = pattern + this.params = params + } + + format(params) { + if (_.isMatch(params, this.params)) { + let toPath = compile(this.pattern, { encode: encodeURIComponent }) + return { + path: toPath(params), + specificity: Object.keys(this.params).length + } + } else { + return null } - } else { - return null } -} -function doParse(path, pattern, extraParams) { - let toPathObject = match(pattern, { decode: decodeURIComponent }) - let pathObject = toPathObject(path) - if (pathObject) { - return { - path: pathObject.path, - ...pathObject.params, - ...extraParams + parse(path) { + let toPathObject = match(this.pattern, { decode: decodeURIComponent }) + let pathObject = toPathObject(path) + if (pathObject) { + return { + path: pathObject.path, + ...pathObject.params, + ...this.params + } + } else { + return null } - } else { - return null } } -const routes = { - TYPES: { - format: params => { - return doFormat(params, '/types', { - page: pages.TYPES - }) - }, - parse: path => { - return doParse(path, '/types', { - page: pages.TYPES - }) - } - }, - TYPES_SEARCH: { - format: params => { - return doFormat(params, '/types-search/:id', { - page: pages.TYPES, - type: objectTypes.SEARCH - }) - }, - parse: path => { - return doParse(path, '/types-search/:id', { - page: pages.TYPES, - type: objectTypes.SEARCH - }) - } - }, - NEW_OBJECT_TYPE: { - format: params => { - return doFormat(params, '/new-object-type/:id', { - page: pages.TYPES, - type: objectTypes.NEW_OBJECT_TYPE - }) - }, - parse: path => { - return doParse(path, '/new-object-type/:id', { - page: pages.TYPES, - type: objectTypes.NEW_OBJECT_TYPE - }) - } - }, - OBJECT_TYPE: { - format: params => { - return doFormat(params, '/object-type/:id', { - page: pages.TYPES, - type: objectTypes.OBJECT_TYPE - }) - }, - parse: path => { - return doParse(path, '/object-type/:id', { - page: pages.TYPES, - type: objectTypes.OBJECT_TYPE - }) - } - }, - NEW_COLLECTION_TYPE: { - format: params => { - return doFormat(params, '/new-collection-type/:id', { - page: pages.TYPES, - type: objectTypes.NEW_COLLECTION_TYPE - }) - }, - parse: path => { - return doParse(path, '/new-collection-type/:id', { - page: pages.TYPES, - type: objectTypes.NEW_COLLECTION_TYPE - }) - } - }, - COLLECTION_TYPE: { - format: params => { - return doFormat(params, '/collection-type/:id', { - page: pages.TYPES, - type: objectTypes.COLLECTION_TYPE - }) - }, - parse: path => { - return doParse(path, '/collection-type/:id', { - page: pages.TYPES, - type: objectTypes.COLLECTION_TYPE - }) - } - }, - NEW_DATA_SET_TYPE: { - format: params => { - return doFormat(params, '/new-dataset-type/:id', { - page: pages.TYPES, - type: objectTypes.NEW_DATA_SET_TYPE - }) - }, - parse: path => { - return doParse(path, '/new-dataset-type/:id', { - page: pages.TYPES, - type: objectTypes.NEW_DATA_SET_TYPE - }) - } - }, - DATA_SET_TYPE: { - format: params => { - return doFormat(params, '/dataset-type/:id', { - page: pages.TYPES, - type: objectTypes.DATA_SET_TYPE - }) - }, - parse: path => { - return doParse(path, '/dataset-type/:id', { - page: pages.TYPES, - type: objectTypes.DATA_SET_TYPE - }) - } - }, - NEW_MATERIAL_TYPE: { - format: params => { - return doFormat(params, '/new-material-type/:id', { - page: pages.TYPES, - type: objectTypes.NEW_MATERIAL_TYPE - }) - }, - parse: path => { - return doParse(path, '/new-material-type/:id', { - page: pages.TYPES, - type: objectTypes.NEW_MATERIAL_TYPE - }) - } - }, - MATERIAL_TYPE: { - format: params => { - return doFormat(params, '/material-type/:id', { - page: pages.TYPES, - type: objectTypes.MATERIAL_TYPE - }) - }, - parse: path => { - return doParse(path, '/material-type/:id', { - page: pages.TYPES, - type: objectTypes.MATERIAL_TYPE - }) - } - }, - NEW_VOCABULARY_TYPE: { - format: params => { - return doFormat(params, '/new-vocabulary-type/:id', { - page: pages.TYPES, - type: objectTypes.NEW_VOCABULARY_TYPE - }) - }, - parse: path => { - return doParse(path, '/new-vocabulary-type/:id', { - page: pages.TYPES, - type: objectTypes.NEW_VOCABULARY_TYPE - }) - } - }, - VOCABULARY_TYPE: { - format: params => { - return doFormat(params, '/vocabulary-type/:id', { - page: pages.TYPES, - type: objectTypes.VOCABULARY_TYPE - }) - }, - parse: path => { - return doParse(path, '/vocabulary-type/:id', { - page: pages.TYPES, - type: objectTypes.VOCABULARY_TYPE - }) - } - }, - USERS: { - format: params => { - return doFormat(params, '/users', { - page: pages.USERS - }) - }, - parse: path => { - return doParse(path, '/users', { - page: pages.USERS - }) - } - }, - USERS_SEARCH: { - format: params => { - return doFormat(params, '/users-search/:id', { - page: pages.USERS, - type: objectTypes.SEARCH - }) - }, - parse: path => { - return doParse(path, '/users-search/:id', { - page: pages.USERS, - type: objectTypes.SEARCH - }) - } - }, - NEW_USER: { - format: params => { - return doFormat(params, '/new-user/:id', { - page: pages.USERS, - type: objectTypes.NEW_USER - }) - }, - parse: path => { - return doParse(path, '/new-user/:id', { - page: pages.USERS, - type: objectTypes.NEW_USER - }) - } - }, - USER: { - format: params => { - return doFormat(params, '/user/:id', { - page: pages.USERS, - type: objectTypes.USER - }) - }, - parse: path => { - return doParse(path, '/user/:id', { - page: pages.USERS, - type: objectTypes.USER - }) - } - }, - NEW_USER_GROUP: { - format: params => { - return doFormat(params, '/new-user-group/:id', { - page: pages.USERS, - type: objectTypes.NEW_USER_GROUP - }) - }, - parse: path => { - return doParse(path, '/new-user-group/:id', { - page: pages.USERS, - type: objectTypes.NEW_USER_GROUP - }) - } - }, - USER_GROUP: { - format: params => { - return doFormat(params, '/user-group/:id', { - page: pages.USERS, - type: objectTypes.USER_GROUP - }) - }, - parse: path => { - return doParse(path, '/user-group/:id', { - page: pages.USERS, - type: objectTypes.USER_GROUP - }) +class DefaultRoute { + format() { + return { + path: '/', + specificity: 0 } - }, - DEFAULT: { - format: () => { - return { - path: '/', - match: 0 - } - }, - parse: () => { - return { - path: '/', - page: pages.TYPES - } + } + parse() { + return { + path: '/', + page: pages.TYPES } } } +const routes = { + TYPES: new Route('/types', { + page: pages.TYPES + }), + TYPES_SEARCH: new Route('/types-search/:id', { + page: pages.TYPES, + type: objectTypes.SEARCH + }), + NEW_OBJECT_TYPE: new Route('/new-object-type/:id', { + page: pages.TYPES, + type: objectTypes.NEW_OBJECT_TYPE + }), + OBJECT_TYPE: new Route('/object-type/:id', { + page: pages.TYPES, + type: objectTypes.OBJECT_TYPE + }), + NEW_COLLECTION_TYPE: new Route('/new-collection-type/:id', { + page: pages.TYPES, + type: objectTypes.NEW_COLLECTION_TYPE + }), + COLLECTION_TYPE: new Route('/collection-type/:id', { + page: pages.TYPES, + type: objectTypes.COLLECTION_TYPE + }), + NEW_DATA_SET_TYPE: new Route('/new-dataset-type/:id', { + page: pages.TYPES, + type: objectTypes.NEW_DATA_SET_TYPE + }), + DATA_SET_TYPE: new Route('/dataset-type/:id', { + page: pages.TYPES, + type: objectTypes.DATA_SET_TYPE + }), + NEW_MATERIAL_TYPE: new Route('/new-material-type/:id', { + page: pages.TYPES, + type: objectTypes.NEW_MATERIAL_TYPE + }), + MATERIAL_TYPE: new Route('/material-type/:id', { + page: pages.TYPES, + type: objectTypes.MATERIAL_TYPE + }), + NEW_VOCABULARY_TYPE: new Route('/new-vocabulary-type/:id', { + page: pages.TYPES, + type: objectTypes.NEW_VOCABULARY_TYPE + }), + VOCABULARY_TYPE: new Route('/vocabulary-type/:id', { + page: pages.TYPES, + type: objectTypes.VOCABULARY_TYPE + }), + USERS: new Route('/users', { + page: pages.USERS + }), + USERS_SEARCH: new Route('/users-search/:id', { + page: pages.USERS, + type: objectTypes.SEARCH + }), + NEW_USER: new Route('/new-user/:id', { + page: pages.USERS, + type: objectTypes.NEW_USER + }), + USER: new Route('/user/:id', { + page: pages.USERS, + type: objectTypes.USER + }), + NEW_USER_GROUP: new Route('/new-user-group/:id', { + page: pages.USERS, + type: objectTypes.NEW_USER_GROUP + }), + USER_GROUP: new Route('/user-group/:id', { + page: pages.USERS, + type: objectTypes.USER_GROUP + }), + TOOLS: new Route('/tools', { + page: pages.TOOLS + }), + TOOLS_SEARCH: new Route('/tools-search/:id', { + page: pages.TOOLS, + type: objectTypes.SEARCH + }), + NEW_DYNAMIC_PROPERTY_PLUGIN: new Route('/new-dynamic-property-plugin/:id', { + page: pages.TOOLS, + type: objectTypes.NEW_DYNAMIC_PROPERTY_PLUGIN + }), + DYNAMIC_PROPERTY_PLUGIN: new Route('/dynamic-property-plugin/:id', { + page: pages.TOOLS, + type: objectTypes.DYNAMIC_PROPERTY_PLUGIN + }), + NEW_ENTITY_VALIDATION_PLUGIN: new Route('/new-entity-validation-plugin/:id', { + page: pages.TOOLS, + type: objectTypes.NEW_ENTITY_VALIDATION_PLUGIN + }), + ENTITY_VALIDATION_PLUGIN: new Route('/entity-validation-plugin/:id', { + page: pages.TOOLS, + type: objectTypes.ENTITY_VALIDATION_PLUGIN + }), + DEFAULT: new DefaultRoute() +} + function format(params) { let keys = Object.keys(routes) - let best = { match: 0, path: null } + let best = { specificity: 0, path: null } for (let i = 0; i < keys.length; i++) { let route = routes[keys[i]] try { let result = route.format(params) - if (result && result.match > best.match) { + if (result && result.specificity > best.specificity) { best = result } } catch (err) { diff --git a/openbis_ng_ui/src/js/components/App.jsx b/openbis_ng_ui/src/js/components/App.jsx index 84495be31ee09d5ec512294e6536a0c378cd43d9..ae970d996d40ee02e4e505e31dc5f331dab32700 100644 --- a/openbis_ng_ui/src/js/components/App.jsx +++ b/openbis_ng_ui/src/js/components/App.jsx @@ -15,6 +15,7 @@ import Menu from '@src/js/components/common/menu/Menu.jsx' import Login from '@src/js/components/login/Login.jsx' import Users from '@src/js/components/users/Users.jsx' import Types from '@src/js/components/types/Types.jsx' +import Tools from '@src/js/components/tools/Tools.jsx' const styles = { container: { @@ -37,7 +38,8 @@ const styles = { const pageToComponent = { [pages.TYPES]: Types, - [pages.USERS]: Users + [pages.USERS]: Users, + [pages.TOOLS]: Tools } function mapStateToProps(state) { diff --git a/openbis_ng_ui/src/js/components/common/form/FormFieldView.jsx b/openbis_ng_ui/src/js/components/common/form/FormFieldView.jsx index 8632fc6f8b49fd303844d35f060df72416036467..b8cd02874c79e827f84584ea9a0742664706acfb 100644 --- a/openbis_ng_ui/src/js/components/common/form/FormFieldView.jsx +++ b/openbis_ng_ui/src/js/components/common/form/FormFieldView.jsx @@ -11,10 +11,7 @@ const styles = theme => ({ paddingBottom: theme.spacing(1) / 2, borderBottomWidth: '1px', borderBottomStyle: 'solid', - borderBottomColor: theme.palette.border.secondary, - '&:after': { - content: '"\\00a0"' - } + borderBottomColor: theme.palette.border.secondary } }) @@ -23,11 +20,11 @@ class FormFieldView extends React.PureComponent { const { label, value, classes } = this.props return ( <div> - <Typography variant='body2' className={classes.label}> + <Typography variant='body2' component='div' className={classes.label}> {label} </Typography> - <Typography variant='body2' className={classes.value}> - {value ? value : ''} + <Typography variant='body2' component='div' className={classes.value}> + {value ? value : <span> </span>} </Typography> </div> ) diff --git a/openbis_ng_ui/src/js/components/common/form/SourceCodeField.jsx b/openbis_ng_ui/src/js/components/common/form/SourceCodeField.jsx new file mode 100644 index 0000000000000000000000000000000000000000..28e510b82787fd7ed7ff71747f49ea2aea78209e --- /dev/null +++ b/openbis_ng_ui/src/js/components/common/form/SourceCodeField.jsx @@ -0,0 +1,202 @@ +import React from 'react' +import autoBind from 'auto-bind' +import { withStyles } from '@material-ui/core/styles' +import { highlight, languages } from 'prismjs/components/prism-core.js' +import 'prismjs/components/prism-clike.js' +import 'prismjs/components/prism-python.js' +import 'prismjs/themes/prism.css' +import Editor from 'react-simple-code-editor' +import InputLabel from '@material-ui/core/InputLabel' +import FormFieldLabel from '@src/js/components/common/form/FormFieldLabel.jsx' +import FormFieldContainer from '@src/js/components/common/form/FormFieldContainer.jsx' +import FormFieldView from '@src/js/components/common/form/FormFieldView.jsx' +import logger from '@src/js/common/logger.js' + +const styles = theme => ({ + view: { + fontFamily: theme.typography.sourceCode.fontFamily, + fontSize: theme.typography.body2.fontSize, + whiteSpace: 'pre-wrap', + tabSize: 4 + }, + + edit: { + fontFamily: theme.typography.sourceCode.fontFamily, + fontSize: theme.typography.body2.fontSize, + lineHeight: theme.typography.body2.lineHeight, + backgroundColor: theme.palette.background.field, + tabSize: 4, + '& *': { + background: 'none !important' + }, + '& textarea': { + padding: '23px 12px 6px 12px !important', + border: `1px solid ${theme.palette.border.primary} !important`, + borderBottom: `1px solid ${theme.palette.border.field} !important`, + outline: 'none !important' + }, + '& textarea:focus': { + borderBottom: `2px solid ${theme.palette.primary.main} !important` + }, + '& pre': { + padding: '23px 12px 6px 12px !important' + } + }, + error: { + '&$edit textarea': { + borderBottom: `2px solid ${theme.palette.error.main} !important` + } + }, + disabled: { + '&$edit pre': { + opacity: 0.5 + } + } +}) + +class SourceCodeField extends React.PureComponent { + static defaultProps = { + mode: 'edit', + variant: 'filled' + } + + constructor(props) { + super(props) + autoBind(this) + this.state = { focused: false } + this.containerRef = React.createRef() + } + + handleValueChange(value) { + const { name, onChange } = this.props + if (onChange) { + onChange({ + target: { + name, + value + } + }) + } + } + + handleFocus() { + this.setState({ + focused: true + }) + + const { onFocus } = this.props + + if (onFocus) { + onFocus(event) + } + } + + handleBlur(event) { + this.setState({ + focused: false + }) + + const { onBlur } = this.props + + if (onBlur) { + onBlur(event) + } + } + + componentDidUpdate() { + const { reference } = this.props + if (reference) { + const containerElement = this.containerRef.current + if (containerElement) { + reference.current = containerElement.querySelector('textarea') + } + } + } + + render() { + logger.log(logger.DEBUG, 'SourceCodeField.render') + + const { mode } = this.props + + if (mode === 'view') { + return this.renderView() + } else if (mode === 'edit') { + return this.renderEdit() + } else { + throw 'Unsupported mode: ' + mode + } + } + + renderView() { + const { label, value, classes } = this.props + const html = { __html: highlight(value || '', languages.python) } + + return ( + <FormFieldView + label={label} + value={<div className={classes.view} dangerouslySetInnerHTML={html} />} + /> + ) + } + + renderEdit() { + const { + name, + label, + value, + description, + mandatory, + disabled, + error, + variant, + onClick, + styles, + classes + } = this.props + + const { focused } = this.state + + return ( + <FormFieldContainer + description={description} + error={error} + styles={styles} + onClick={onClick} + > + <div + ref={this.containerRef} + className={` + ${classes.edit} + ${error ? classes.error : ''} + ${disabled ? classes.disabled : ''} + `} + > + <InputLabel + shrink={!!value || focused} + error={!!error} + variant={variant} + margin='dense' + > + <FormFieldLabel + label={label} + mandatory={mandatory} + styles={styles} + onClick={onClick} + /> + </InputLabel> + <Editor + name={name} + value={value || ''} + highlight={code => highlight(code, languages.python)} + disabled={disabled} + onValueChange={this.handleValueChange} + onFocus={this.handleFocus} + onBlur={this.handleBlur} + /> + </div> + </FormFieldContainer> + ) + } +} + +export default withStyles(styles)(SourceCodeField) diff --git a/openbis_ng_ui/src/js/components/common/menu/Menu.jsx b/openbis_ng_ui/src/js/components/common/menu/Menu.jsx index 1da0c2b3724a8254e065847a015b599f5cc8b850..7902417a643d9b055ee68a3a8557bf600112ea01 100644 --- a/openbis_ng_ui/src/js/components/common/menu/Menu.jsx +++ b/openbis_ng_ui/src/js/components/common/menu/Menu.jsx @@ -113,6 +113,7 @@ class Menu extends React.Component { > <Tab value={pages.TYPES} label='Types' /> <Tab value={pages.USERS} label='Users' /> + <Tab value={pages.TOOLS} label='Tools' /> </Tabs> <TextField placeholder='Search...' diff --git a/openbis_ng_ui/src/js/components/common/theme/ThemeProvider.jsx b/openbis_ng_ui/src/js/components/common/theme/ThemeProvider.jsx index f67e2f45d72f35a08890e3486b9b5b7c3bc1643f..2088b09777b35f5d27fab0d066a8e3eb5af4e489 100644 --- a/openbis_ng_ui/src/js/components/common/theme/ThemeProvider.jsx +++ b/openbis_ng_ui/src/js/components/common/theme/ThemeProvider.jsx @@ -9,6 +9,9 @@ const config = { label: { fontSize: '0.7rem', color: '#0000008a' + }, + sourceCode: { + fontFamily: '"Fira code", "Fira Mono", monospace' } }, palette: { @@ -29,11 +32,13 @@ const config = { }, background: { primary: '#ebebeb', - secondary: '#f5f5f5' + secondary: '#f5f5f5', + field: '#e8e8e8' }, border: { primary: '#dbdbdb', - secondary: '#ebebeb' + secondary: '#ebebeb', + field: '#878787' } } } diff --git a/openbis_ng_ui/src/js/components/tools/Tools.jsx b/openbis_ng_ui/src/js/components/tools/Tools.jsx new file mode 100644 index 0000000000000000000000000000000000000000..ea4477897b883a58b349a0a9cbbd5bcda5c31d48 --- /dev/null +++ b/openbis_ng_ui/src/js/components/tools/Tools.jsx @@ -0,0 +1,67 @@ +import React from 'react' +import { withStyles } from '@material-ui/core/styles' +import logger from '@src/js/common/logger.js' +import pages from '@src/js/common/consts/pages.js' +import objectType from '@src/js/common/consts/objectType.js' +import Content from '@src/js/components/common/content/Content.jsx' +import ContentTab from '@src/js/components/common/content/ContentTab.jsx' +import ToolBrowser from '@src/js/components/tools/browser/ToolBrowser.jsx' +import ToolSearch from '@src/js/components/tools/search/ToolSearch.jsx' +import PluginForm from '@src/js/components/tools/form/plugin/PluginForm.jsx' + +const styles = () => ({ + container: { + display: 'flex', + width: '100%' + } +}) + +class Tools extends React.Component { + render() { + logger.log(logger.DEBUG, 'Tools.render') + + const classes = this.props.classes + + return ( + <div className={classes.container}> + <ToolBrowser /> + <Content + page={pages.TOOLS} + renderComponent={this.renderComponent} + renderTab={this.renderTab} + /> + </div> + ) + } + + renderComponent(tab) { + const { object } = tab + if ( + object.type === objectType.NEW_DYNAMIC_PROPERTY_PLUGIN || + object.type === objectType.NEW_ENTITY_VALIDATION_PLUGIN || + object.type === objectType.DYNAMIC_PROPERTY_PLUGIN || + object.type === objectType.ENTITY_VALIDATION_PLUGIN + ) { + return <PluginForm object={object} /> + } else if (object.type === objectType.SEARCH) { + return <ToolSearch objectId={object.id} /> + } + } + + renderTab(tab) { + const { object } = tab + + const prefixes = { + [objectType.DYNAMIC_PROPERTY_PLUGIN]: 'Dynamic Property Plugin: ', + [objectType.NEW_DYNAMIC_PROPERTY_PLUGIN]: 'New Dynamic Property Plugin ', + [objectType.ENTITY_VALIDATION_PLUGIN]: 'Entity Validation Plugin: ', + [objectType.NEW_ENTITY_VALIDATION_PLUGIN]: + 'New Entity Validation Plugin ', + [objectType.SEARCH]: 'Search: ' + } + + return <ContentTab prefix={prefixes[object.type]} tab={tab} /> + } +} + +export default withStyles(styles)(Tools) diff --git a/openbis_ng_ui/src/js/components/tools/browser/ToolBrowser.jsx b/openbis_ng_ui/src/js/components/tools/browser/ToolBrowser.jsx new file mode 100644 index 0000000000000000000000000000000000000000..f306d4f7cad2df71004085214d5ff9d7a3241419 --- /dev/null +++ b/openbis_ng_ui/src/js/components/tools/browser/ToolBrowser.jsx @@ -0,0 +1,18 @@ +import React from 'react' +import Browser from '@src/js/components/common/browser/Browser.jsx' +import ToolBrowserController from '@src/js/components/tools/browser/ToolBrowserController.js' +import logger from '@src/js/common/logger.js' + +class ToolBrowser extends React.Component { + constructor(props) { + super(props) + this.controller = this.props.controller || new ToolBrowserController() + } + + render() { + logger.log(logger.DEBUG, 'ToolBrowser.render') + return <Browser controller={this.controller} /> + } +} + +export default ToolBrowser diff --git a/openbis_ng_ui/src/js/components/tools/browser/ToolBrowserController.js b/openbis_ng_ui/src/js/components/tools/browser/ToolBrowserController.js new file mode 100644 index 0000000000000000000000000000000000000000..9c9f18b2e7be7eaf7ac75dbe05849550aa824014 --- /dev/null +++ b/openbis_ng_ui/src/js/components/tools/browser/ToolBrowserController.js @@ -0,0 +1,141 @@ +import openbis from '@src/js/services/openbis.js' +import actions from '@src/js/store/actions/actions.js' +import pages from '@src/js/common/consts/pages.js' +import objectType from '@src/js/common/consts/objectType.js' +import objectOperation from '@src/js/common/consts/objectOperation.js' +import BrowserController from '@src/js/components/common/browser/BrowserController.js' + +export default class ToolBrowserController extends BrowserController { + doGetPage() { + return pages.TOOLS + } + + async doLoadNodes() { + return Promise.all([ + openbis.searchPlugins( + new openbis.PluginSearchCriteria(), + new openbis.PluginFetchOptions() + ) + ]).then(([plugins]) => { + const dynamicPropertyPluginNodes = plugins + .getObjects() + .filter( + plugin => plugin.pluginType === openbis.PluginType.DYNAMIC_PROPERTY + ) + .map(plugin => { + return { + id: `dynamicPropertyPlugin/${plugin.name}`, + text: plugin.name, + object: { + type: objectType.DYNAMIC_PROPERTY_PLUGIN, + id: plugin.name + }, + canMatchFilter: true, + canRemove: true + } + }) + + const entityValidationPluginNodes = plugins + .getObjects() + .filter( + plugin => plugin.pluginType === openbis.PluginType.ENTITY_VALIDATION + ) + .map(plugin => { + return { + id: `entityValidationPlugin/${plugin.name}`, + text: plugin.name, + object: { + type: objectType.ENTITY_VALIDATION_PLUGIN, + id: plugin.name + }, + canMatchFilter: true, + canRemove: true + } + }) + + let nodes = [ + { + id: 'dynamicPropertyPlugins', + text: 'Dynamic Property Plugins', + children: dynamicPropertyPluginNodes, + childrenType: objectType.NEW_DYNAMIC_PROPERTY_PLUGIN, + canAdd: true + }, + { + id: 'entityValidationPlugins', + text: 'Entity Validation Plugins', + children: entityValidationPluginNodes, + childrenType: objectType.NEW_ENTITY_VALIDATION_PLUGIN, + canAdd: true + } + ] + + return nodes + }) + } + + doNodeAdd(node) { + if (node && node.childrenType) { + this.context.dispatch( + actions.objectNew(this.getPage(), node.childrenType) + ) + } + } + + doNodeRemove(node) { + if (!node.object) { + return Promise.resolve() + } + + const { type, id } = node.object + const reason = 'deleted via ng_ui' + + return this._prepareRemoveOperations(type, id, reason) + .then(operations => { + const options = new openbis.SynchronousOperationExecutionOptions() + options.setExecuteInOrder(true) + return openbis.executeOperations(operations, options) + }) + .then(() => { + this.context.dispatch(actions.objectDelete(this.getPage(), type, id)) + }) + .catch(error => { + this.context.dispatch(actions.errorChange(error)) + }) + } + + _prepareRemoveOperations(type, id, reason) { + if ( + type === objectType.DYNAMIC_PROPERTY_PLUGIN || + type === objectType.ENTITY_VALIDATION_PLUGIN + ) { + return this._prepareRemovePluginOperations(id, reason) + } else { + throw new Error('Unsupported type: ' + type) + } + } + + _prepareRemovePluginOperations(id, reason) { + const options = new openbis.PluginDeletionOptions() + options.setReason(reason) + return Promise.resolve([ + new openbis.DeletePluginsOperation( + [new openbis.PluginPermId(id)], + options + ) + ]) + } + + doGetObservedModifications() { + return { + [objectType.DYNAMIC_PROPERTY_PLUGIN]: [ + objectOperation.CREATE, + objectOperation.DELETE + ], + [objectType.ENTITY_VALIDATION_PLUGIN]: [ + objectOperation.CREATE, + objectOperation.DELETE + ] + } + } +} diff --git a/openbis_ng_ui/src/js/components/tools/form/plugin/PluginForm.jsx b/openbis_ng_ui/src/js/components/tools/form/plugin/PluginForm.jsx new file mode 100644 index 0000000000000000000000000000000000000000..9ecbede7d4ee1795196bcfd24d64282f68e9e6cd --- /dev/null +++ b/openbis_ng_ui/src/js/components/tools/form/plugin/PluginForm.jsx @@ -0,0 +1,108 @@ +import _ from 'lodash' +import React from 'react' +import autoBind from 'auto-bind' +import { connect } from 'react-redux' +import { withStyles } from '@material-ui/core/styles' +import ComponentContext from '@src/js/components/common/ComponentContext.js' +import PageWithTwoPanels from '@src/js/components/common/page/PageWithTwoPanels.jsx' +import PluginFormController from '@src/js/components/tools/form/plugin/PluginFormController.js' +import PluginFormFacade from '@src/js/components/tools/form/plugin/PluginFormFacade.js' +import PluginFormScript from '@src/js/components/tools/form/plugin/PluginFormScript.jsx' +import PluginFormParameters from '@src/js/components/tools/form/plugin/PluginFormParameters.jsx' +import PluginFormButtons from '@src/js/components/tools/form/plugin/PluginFormButtons.jsx' +import openbis from '@src/js/services/openbis.js' +import logger from '@src/js/common/logger.js' + +const styles = () => ({}) + +class PluginForm extends React.PureComponent { + constructor(props) { + super(props) + autoBind(this) + + this.state = {} + + if (this.props.controller) { + this.controller = this.props.controller + } else { + this.controller = new PluginFormController(new PluginFormFacade()) + } + + this.controller.init(new ComponentContext(this)) + } + + componentDidMount() { + this.controller.load() + } + + render() { + logger.log(logger.DEBUG, 'PluginForm.render') + + const { loading, loaded, plugin } = this.state + + return ( + <PageWithTwoPanels + loading={loading} + loaded={loaded} + object={plugin} + renderMainPanel={() => this.renderMainPanel()} + renderAdditionalPanel={() => this.renderAdditionalPanel()} + renderButtons={() => this.renderButtons()} + /> + ) + } + + renderMainPanel() { + const { controller } = this + const { plugin, selection, mode } = this.state + + if (plugin.pluginKind === openbis.PluginKind.JYTHON) { + return ( + <PluginFormScript + plugin={plugin} + selection={selection} + mode={mode} + onChange={controller.handleChange} + onSelectionChange={controller.handleSelectionChange} + onBlur={controller.handleBlur} + /> + ) + } else { + return <div></div> + } + } + + renderAdditionalPanel() { + const { controller } = this + const { plugin, selection, mode } = this.state + + return ( + <PluginFormParameters + plugin={plugin} + selection={selection} + mode={mode} + onChange={controller.handleChange} + onSelectionChange={controller.handleSelectionChange} + onBlur={controller.handleBlur} + /> + ) + } + + renderButtons() { + const { controller } = this + const { plugin, changed, mode } = this.state + + return ( + <PluginFormButtons + onEdit={controller.handleEdit} + onSave={controller.handleSave} + onCancel={controller.handleCancel} + plugin={plugin} + changed={changed} + mode={mode} + /> + ) + } +} + +export default _.flow(connect(), withStyles(styles))(PluginForm) diff --git a/openbis_ng_ui/src/js/components/tools/form/plugin/PluginFormButtons.jsx b/openbis_ng_ui/src/js/components/tools/form/plugin/PluginFormButtons.jsx new file mode 100644 index 0000000000000000000000000000000000000000..0552368188b77e590ea4c45fd2d3bdd90a02002d --- /dev/null +++ b/openbis_ng_ui/src/js/components/tools/form/plugin/PluginFormButtons.jsx @@ -0,0 +1,24 @@ +import React from 'react' +import PageButtons from '@src/js/components/common/page/PageButtons.jsx' +import openbis from '@src/js/services/openbis.js' +import logger from '@src/js/common/logger.js' + +class PluginFormButtons extends React.PureComponent { + render() { + logger.log(logger.DEBUG, 'PluginFormButtons.render') + + const { mode, onEdit, onSave, onCancel, changed, plugin } = this.props + + return ( + <PageButtons + mode={mode} + changed={changed} + onEdit={plugin.pluginKind === openbis.PluginKind.JYTHON ? onEdit : null} + onSave={onSave} + onCancel={plugin.id ? onCancel : null} + /> + ) + } +} + +export default PluginFormButtons diff --git a/openbis_ng_ui/src/js/components/tools/form/plugin/PluginFormController.js b/openbis_ng_ui/src/js/components/tools/form/plugin/PluginFormController.js new file mode 100644 index 0000000000000000000000000000000000000000..0d7ab8d8ba4880b43495b4557e927bf0ca1aab6c --- /dev/null +++ b/openbis_ng_ui/src/js/components/tools/form/plugin/PluginFormController.js @@ -0,0 +1,67 @@ +import PageController from '@src/js/components/common/page/PageController.js' +import PluginFormControllerLoad from '@src/js/components/tools/form/plugin/PluginFormControllerLoad.js' +import PluginFormControllerValidate from '@src/js/components/tools/form/plugin/PluginFormControllerValidate.js' +import PluginFormControllerChange from '@src/js/components/tools/form/plugin/PluginFormControllerChange.js' +import PluginFormControllerSave from '@src/js/components/tools/form/plugin/PluginFormControllerSave.js' +import pages from '@src/js/common/consts/pages.js' +import objectTypes from '@src/js/common/consts/objectType.js' + +export default class PluginFormController extends PageController { + constructor(facade) { + super(facade) + } + + getPage() { + return pages.TOOLS + } + + isDynamicPropertyType() { + return ( + this.object.type === objectTypes.DYNAMIC_PROPERTY_PLUGIN || + this.object.type === objectTypes.NEW_DYNAMIC_PROPERTY_PLUGIN + ) + } + + isEntityValidationType() { + return ( + this.object.type === objectTypes.ENTITY_VALIDATION_PLUGIN || + this.object.type === objectTypes.NEW_ENTITY_VALIDATION_PLUGIN + ) + } + + getNewObjectType() { + if (this.isDynamicPropertyType()) { + return objectTypes.NEW_DYNAMIC_PROPERTY_PLUGIN + } else if (this.isEntityValidationType()) { + return objectTypes.NEW_ENTITY_VALIDATION_PLUGIN + } else { + throw new Error('Unsupported object type: ' + this.object.type) + } + } + + getExistingObjectType() { + if (this.isDynamicPropertyType()) { + return objectTypes.DYNAMIC_PROPERTY_PLUGIN + } else if (this.isEntityValidationType()) { + return objectTypes.ENTITY_VALIDATION_PLUGIN + } else { + throw new Error('Unsupported object type: ' + this.object.type) + } + } + + load() { + return new PluginFormControllerLoad(this).execute() + } + + validate(autofocus) { + return new PluginFormControllerValidate(this).execute(autofocus) + } + + handleChange(type, params) { + return new PluginFormControllerChange(this).execute(type, params) + } + + handleSave() { + return new PluginFormControllerSave(this).execute() + } +} diff --git a/openbis_ng_ui/src/js/components/tools/form/plugin/PluginFormControllerChange.js b/openbis_ng_ui/src/js/components/tools/form/plugin/PluginFormControllerChange.js new file mode 100644 index 0000000000000000000000000000000000000000..70d8722b18b9206b19c947030d0dcf35be069f03 --- /dev/null +++ b/openbis_ng_ui/src/js/components/tools/form/plugin/PluginFormControllerChange.js @@ -0,0 +1,25 @@ +import PageControllerChange from '@src/js/components/common/page/PageControllerChange.js' +import PluginFormSelectionType from '@src/js/components/tools/form/plugin/PluginFormSelectionType.js' +import FormUtil from '@src/js/components/common/form/FormUtil.js' + +export default class PluginFormControllerChange extends PageControllerChange { + async execute(type, params) { + if (type === PluginFormSelectionType.PLUGIN) { + await this._handleChangePlugin(params) + } + } + + async _handleChangePlugin(params) { + await this.context.setState(state => { + const { newObject } = FormUtil.changeObjectField( + state.plugin, + params.field, + params.value + ) + return { + plugin: newObject + } + }) + await this.controller.changed(true) + } +} diff --git a/openbis_ng_ui/src/js/components/tools/form/plugin/PluginFormControllerLoad.js b/openbis_ng_ui/src/js/components/tools/form/plugin/PluginFormControllerLoad.js new file mode 100644 index 0000000000000000000000000000000000000000..c6609a04d51df1035165095f528754d794a9f662 --- /dev/null +++ b/openbis_ng_ui/src/js/components/tools/form/plugin/PluginFormControllerLoad.js @@ -0,0 +1,72 @@ +import _ from 'lodash' +import PageControllerLoad from '@src/js/components/common/page/PageControllerLoad.js' +import FormUtil from '@src/js/components/common/form/FormUtil.js' +import openbis from '@src/js/services/openbis.js' + +export default class PluginFormControllerLoad extends PageControllerLoad { + async load(object, isNew) { + let loadedPlugin = null + + if (!isNew) { + loadedPlugin = await this.facade.loadPlugin(object.id) + if (!loadedPlugin) { + return + } + } + + const plugin = this._createPlugin(object, loadedPlugin) + + return this.context.setState({ + plugin + }) + } + + _createPlugin(object, loadedPlugin) { + let pluginKind = null + let pluginType = null + + if (loadedPlugin) { + pluginKind = _.get(loadedPlugin, 'pluginKind') + pluginType = _.get(loadedPlugin, 'pluginType') + } else { + pluginKind = openbis.PluginKind.JYTHON + + if (this.controller.isDynamicPropertyType()) { + pluginType = openbis.PluginType.DYNAMIC_PROPERTY + } else if (this.controller.isEntityValidationType()) { + pluginType = openbis.PluginType.ENTITY_VALIDATION + } else { + throw new Error('Unsupported object type: ' + object.type) + } + } + + const entityKinds = _.get(loadedPlugin, 'entityKinds', []) + + const plugin = { + id: _.get(loadedPlugin, 'name', null), + pluginKind, + pluginType, + name: FormUtil.createField({ + value: _.get(loadedPlugin, 'name', null), + enabled: loadedPlugin === null + }), + entityKind: FormUtil.createField({ + value: entityKinds.length === 1 ? entityKinds[0] : null, + enabled: loadedPlugin === null + }), + description: FormUtil.createField({ + value: _.get(loadedPlugin, 'description', null) + }), + script: FormUtil.createField({ + value: _.get(loadedPlugin, 'script', null) + }), + available: FormUtil.createField({ + value: _.get(loadedPlugin, 'available', true) + }) + } + if (loadedPlugin) { + plugin.original = _.cloneDeep(plugin) + } + return plugin + } +} diff --git a/openbis_ng_ui/src/js/components/tools/form/plugin/PluginFormControllerSave.js b/openbis_ng_ui/src/js/components/tools/form/plugin/PluginFormControllerSave.js new file mode 100644 index 0000000000000000000000000000000000000000..4b97f1ed27f2c6edee57d583c34f2599b62adb14 --- /dev/null +++ b/openbis_ng_ui/src/js/components/tools/form/plugin/PluginFormControllerSave.js @@ -0,0 +1,51 @@ +import PageControllerSave from '@src/js/components/common/page/PageControllerSave.js' +import FormUtil from '@src/js/components/common/form/FormUtil.js' +import openbis from '@src/js/services/openbis.js' + +export default class PluginFormControllerSave extends PageControllerSave { + async save() { + const state = this.context.getState() + + const plugin = FormUtil.trimFields({ ...state.plugin }) + const operations = [] + + if (plugin.original) { + if (this._isPluginUpdateNeeded(plugin)) { + operations.push(this._updatePluginOperation(plugin)) + } + } else { + operations.push(this._createPluginOperation(plugin)) + } + + const options = new openbis.SynchronousOperationExecutionOptions() + options.setExecuteInOrder(true) + await this.facade.executeOperations(operations, options) + + return plugin.name.value + } + + _isPluginUpdateNeeded(plugin) { + return FormUtil.haveFieldsChanged(plugin, plugin.original, [ + 'description', + 'script' + ]) + } + + _createPluginOperation(plugin) { + const creation = new openbis.PluginCreation() + creation.setPluginType(plugin.pluginType) + creation.setEntityKind(plugin.entityKind.value) + creation.setName(plugin.name.value) + creation.setDescription(plugin.description.value) + creation.setScript(plugin.script.value) + return new openbis.CreatePluginsOperation([creation]) + } + + _updatePluginOperation(plugin) { + const update = new openbis.PluginUpdate() + update.setPluginId(new openbis.PluginPermId(plugin.name.value)) + update.setDescription(plugin.description.value) + update.setScript(plugin.script.value) + return new openbis.UpdatePluginsOperation([update]) + } +} diff --git a/openbis_ng_ui/src/js/components/tools/form/plugin/PluginFormControllerValidate.js b/openbis_ng_ui/src/js/components/tools/form/plugin/PluginFormControllerValidate.js new file mode 100644 index 0000000000000000000000000000000000000000..3a2d00be9622b911d6a18e0c74d15a354542e66b --- /dev/null +++ b/openbis_ng_ui/src/js/components/tools/form/plugin/PluginFormControllerValidate.js @@ -0,0 +1,33 @@ +import PageControllerValidate from '@src/js/components/common/page/PageConrollerValidate.js' +import PluginFormSelectionType from '@src/js/components/tools/form/plugin/PluginFormSelectionType.js' + +export default class PluginFormControllerValidate extends PageControllerValidate { + validate(validator) { + const { plugin } = this.context.getState() + + const newPlugin = this._validatePlugin(validator, plugin) + + return { + plugin: newPlugin + } + } + + async select(firstError) { + const { plugin } = this.context.getState() + + if (firstError.object === plugin) { + await this.setSelection({ + type: PluginFormSelectionType.PLUGIN, + params: { + part: firstError.name + } + }) + } + } + + _validatePlugin(validator, plugin) { + validator.validateNotEmpty(plugin, 'name', 'Name') + validator.validateNotEmpty(plugin, 'script', 'Script') + return validator.withErrors(plugin) + } +} diff --git a/openbis_ng_ui/src/js/components/tools/form/plugin/PluginFormFacade.js b/openbis_ng_ui/src/js/components/tools/form/plugin/PluginFormFacade.js new file mode 100644 index 0000000000000000000000000000000000000000..5bf01ba940ff32fd544f47dc1c0b6423dd0e055d --- /dev/null +++ b/openbis_ng_ui/src/js/components/tools/form/plugin/PluginFormFacade.js @@ -0,0 +1,16 @@ +import openbis from '@src/js/services/openbis.js' + +export default class PluginFormFacade { + async loadPlugin(pluginName) { + const id = new openbis.PluginPermId(pluginName) + const fo = new openbis.PluginFetchOptions() + fo.withScript() + return openbis.getPlugins([id], fo).then(map => { + return map[pluginName] + }) + } + + async executeOperations(operations, options) { + return openbis.executeOperations(operations, options) + } +} diff --git a/openbis_ng_ui/src/js/components/tools/form/plugin/PluginFormParameters.jsx b/openbis_ng_ui/src/js/components/tools/form/plugin/PluginFormParameters.jsx new file mode 100644 index 0000000000000000000000000000000000000000..f8da7b61b8355cf08598976d67a99ebee1f6a3c8 --- /dev/null +++ b/openbis_ng_ui/src/js/components/tools/form/plugin/PluginFormParameters.jsx @@ -0,0 +1,216 @@ +import React from 'react' +import { withStyles } from '@material-ui/core/styles' +import Container from '@src/js/components/common/form/Container.jsx' +import Header from '@src/js/components/common/form/Header.jsx' +import Message from '@src/js/components/common/form/Message.jsx' +import TextField from '@src/js/components/common/form/TextField.jsx' +import SelectField from '@src/js/components/common/form/SelectField.jsx' +import PluginFormSelectionType from '@src/js/components/tools/form/plugin/PluginFormSelectionType.js' +import openbis from '@src/js/services/openbis.js' +import logger from '@src/js/common/logger.js' + +const styles = theme => ({ + field: { + paddingBottom: theme.spacing(1) + } +}) + +class PluginFormParameters extends React.PureComponent { + constructor(props) { + super(props) + this.state = {} + this.references = { + name: React.createRef(), + entityKind: React.createRef(), + description: React.createRef() + } + this.handleChange = this.handleChange.bind(this) + this.handleFocus = this.handleFocus.bind(this) + this.handleBlur = this.handleBlur.bind(this) + } + + componentDidMount() { + this.focus() + } + + componentDidUpdate(prevProps) { + const prevSelection = prevProps.selection + const selection = this.props.selection + + if (prevSelection !== selection) { + this.focus() + } + } + + focus() { + if (this.props.selection) { + const { part } = this.props.selection.params + if (part) { + const reference = this.references[part] + if (reference && reference.current) { + reference.current.focus() + } + } + } + } + + handleChange(event) { + this.props.onChange(PluginFormSelectionType.PLUGIN, { + field: event.target.name, + value: event.target.value + }) + } + + handleFocus(event) { + this.props.onSelectionChange(PluginFormSelectionType.PLUGIN, { + part: event.target.name + }) + } + + handleBlur() { + this.props.onBlur() + } + + render() { + logger.log(logger.DEBUG, 'PluginFormParameters.render') + + const { plugin } = this.props + + return ( + <Container> + <Header>Plugin</Header> + {this.renderMessageDisabled(plugin)} + {this.renderMessagePredeployed(plugin)} + {this.renderName(plugin)} + {this.renderEntityKind(plugin)} + {this.renderDescription(plugin)} + </Container> + ) + } + + renderMessageDisabled(plugin) { + const { classes } = this.props + + if (!plugin.available.value) { + return ( + <div className={classes.field}> + <Message type='warning'>The plugin is disabled.</Message> + </div> + ) + } else { + return null + } + } + + renderMessagePredeployed(plugin) { + const { classes } = this.props + + if (plugin.pluginKind === openbis.PluginKind.PREDEPLOYED) { + return ( + <div className={classes.field}> + <Message type='info'> + This is a predeployed Java plugin. Its parameters and logic are + defined in the plugin Java class and therefore cannot be changed + from the UI. + </Message> + </div> + ) + } else { + return null + } + } + + renderName(plugin) { + const { visible, enabled, error, value } = { ...plugin.name } + + if (!visible) { + return null + } + + const { mode, classes } = this.props + return ( + <div className={classes.field}> + <TextField + reference={this.references.name} + label='Name' + name='name' + mandatory={true} + error={error} + disabled={!enabled} + value={value} + mode={mode} + onChange={this.handleChange} + onFocus={this.handleFocus} + onBlur={this.handleBlur} + /> + </div> + ) + } + + renderEntityKind(plugin) { + const { visible, enabled, error, value } = { ...plugin.entityKind } + + if (!visible) { + return null + } + + const { mode, classes } = this.props + + const options = openbis.EntityKind.values.map(entityKind => { + return { + label: entityKind, + value: entityKind + } + }) + + return ( + <div className={classes.field}> + <SelectField + reference={this.references.entityKind} + label='Entity Kind' + name='entityKind' + error={error} + disabled={!enabled} + value={value} + options={options} + emptyOption={{ + label: '(all)', + selectable: true + }} + mode={mode} + onChange={this.handleChange} + onFocus={this.handleFocus} + onBlur={this.handleBlur} + /> + </div> + ) + } + + renderDescription(plugin) { + const { visible, enabled, error, value } = { ...plugin.description } + + if (!visible) { + return null + } + + const { mode, classes } = this.props + return ( + <div className={classes.field}> + <TextField + reference={this.references.description} + label='Description' + name='description' + error={error} + disabled={!enabled} + value={value} + mode={mode} + onChange={this.handleChange} + onFocus={this.handleFocus} + onBlur={this.handleBlur} + /> + </div> + ) + } +} + +export default withStyles(styles)(PluginFormParameters) diff --git a/openbis_ng_ui/src/js/components/tools/form/plugin/PluginFormScript.jsx b/openbis_ng_ui/src/js/components/tools/form/plugin/PluginFormScript.jsx new file mode 100644 index 0000000000000000000000000000000000000000..b586abcfd2d9e14d24d4fb7f164d4f3e9dd1cb3c --- /dev/null +++ b/openbis_ng_ui/src/js/components/tools/form/plugin/PluginFormScript.jsx @@ -0,0 +1,106 @@ +import React from 'react' +import { withStyles } from '@material-ui/core/styles' +import Container from '@src/js/components/common/form/Container.jsx' +import Header from '@src/js/components/common/form/Header.jsx' +import SourceCodeField from '@src/js/components/common/form/SourceCodeField.jsx' +import PluginFormSelectionType from '@src/js/components/tools/form/plugin/PluginFormSelectionType.js' +import logger from '@src/js/common/logger.js' + +const styles = () => ({}) + +class PluginFormScript extends React.PureComponent { + constructor(props) { + super(props) + this.state = {} + this.references = { + script: React.createRef() + } + this.handleChange = this.handleChange.bind(this) + this.handleFocus = this.handleFocus.bind(this) + this.handleBlur = this.handleBlur.bind(this) + } + + componentDidMount() { + this.focus() + } + + componentDidUpdate(prevProps) { + const prevSelection = prevProps.selection + const selection = this.props.selection + + if (prevSelection !== selection) { + this.focus() + } + } + + focus() { + if (this.props.selection) { + const { part } = this.props.selection.params + if (part) { + const reference = this.references[part] + if (reference && reference.current) { + reference.current.focus() + } + } + } + } + + handleChange(event) { + this.props.onChange(PluginFormSelectionType.PLUGIN, { + field: event.target.name, + value: event.target.value + }) + } + + handleFocus(event) { + this.props.onSelectionChange(PluginFormSelectionType.PLUGIN, { + part: event.target.name + }) + } + + handleBlur() { + this.props.onBlur() + } + + render() { + logger.log(logger.DEBUG, 'PluginFormScript.render') + + const { plugin } = this.props + + return ( + <Container> + <Header>Script</Header> + {this.renderScript(plugin)} + </Container> + ) + } + + renderScript(plugin) { + const { visible, enabled, error, value } = { ...plugin.script } + + if (!visible) { + return null + } + + const { mode, classes } = this.props + return ( + <div className={classes.field}> + <SourceCodeField + reference={this.references.script} + label='Script' + name='script' + mandatory={true} + error={error} + disabled={!enabled} + value={value} + mode={mode} + onChange={this.handleChange} + onFocus={this.handleFocus} + onBlur={this.handleBlur} + /> + </div> + ) + } +} + +export default withStyles(styles)(PluginFormScript) diff --git a/openbis_ng_ui/src/js/components/tools/form/plugin/PluginFormSelectionType.js b/openbis_ng_ui/src/js/components/tools/form/plugin/PluginFormSelectionType.js new file mode 100644 index 0000000000000000000000000000000000000000..56bde9532baa625abe32e4689db6f6726549cf44 --- /dev/null +++ b/openbis_ng_ui/src/js/components/tools/form/plugin/PluginFormSelectionType.js @@ -0,0 +1,5 @@ +const PLUGIN = 'plugins' + +export default { + PLUGIN +} diff --git a/openbis_ng_ui/src/js/components/tools/search/ToolSearch.jsx b/openbis_ng_ui/src/js/components/tools/search/ToolSearch.jsx new file mode 100644 index 0000000000000000000000000000000000000000..b3bb7f3e0e02b5bbfa4a068c784833f45da4915f --- /dev/null +++ b/openbis_ng_ui/src/js/components/tools/search/ToolSearch.jsx @@ -0,0 +1,9 @@ +import React from 'react' +import logger from '@src/js/common/logger.js' + +export default class ToolSearch extends React.Component { + render() { + logger.log(logger.DEBUG, 'ToolSearch.render') + return 'ToolSearch' + } +} diff --git a/openbis_ng_ui/src/js/services/openbis/api.js b/openbis_ng_ui/src/js/services/openbis/api.js index ad8b711018b0067b9a56453b6b81bd1837f60933..baa362e798f146a99388dd8cf7b887a21068e7ba 100644 --- a/openbis_ng_ui/src/js/services/openbis/api.js +++ b/openbis_ng_ui/src/js/services/openbis/api.js @@ -31,6 +31,10 @@ class Facade { return this.promise(this.v3.logout()) } + getPlugins(ids, fo) { + return this.promise(this.v3.getPlugins(ids, fo)) + } + getPropertyTypes(ids, fo) { return this.promise(this.v3.getPropertyTypes(ids, fo)) } diff --git a/openbis_ng_ui/src/js/services/openbis/dto.js b/openbis_ng_ui/src/js/services/openbis/dto.js index 7389cf14e4dd5f2baec95812a44975395dc704af..95c047f4e5a5e3b689b6b04d8b55d0ff7222d9dd 100644 --- a/openbis_ng_ui/src/js/services/openbis/dto.js +++ b/openbis_ng_ui/src/js/services/openbis/dto.js @@ -52,7 +52,14 @@ const CLASS_FULL_NAMES = [ 'as/dto/person/search/PersonSearchCriteria', 'as/dto/person/update/PersonUpdate', 'as/dto/person/update/UpdatePersonsOperation', + 'as/dto/plugin/PluginKind', 'as/dto/plugin/PluginType', + 'as/dto/plugin/create/CreatePluginsOperation', + 'as/dto/plugin/create/PluginCreation', + 'as/dto/plugin/update/PluginUpdate', + 'as/dto/plugin/update/UpdatePluginsOperation', + 'as/dto/plugin/delete/PluginDeletionOptions', + 'as/dto/plugin/delete/DeletePluginsOperation', 'as/dto/plugin/fetchoptions/PluginFetchOptions', 'as/dto/plugin/id/PluginPermId', 'as/dto/plugin/search/PluginSearchCriteria', diff --git a/openbis_ng_ui/src/js/store/reducers/ui/pages/pages.js b/openbis_ng_ui/src/js/store/reducers/ui/pages/pages.js index 5192825f1ce556a49b36321d2b47aa4367cd3630..97fdf85fb2109f5fed3da305c23f2cc03dc42f50 100644 --- a/openbis_ng_ui/src/js/store/reducers/ui/pages/pages.js +++ b/openbis_ng_ui/src/js/store/reducers/ui/pages/pages.js @@ -2,9 +2,11 @@ import { combineReducers } from 'redux' import login from '@src/js/store/reducers/ui/pages/login/login.js' import types from '@src/js/store/reducers/ui/pages/types/types.js' import users from '@src/js/store/reducers/ui/pages/users/users.js' +import tools from '@src/js/store/reducers/ui/pages/tools/tools.js' export default combineReducers({ login, types, - users + users, + tools }) diff --git a/openbis_ng_ui/src/js/store/reducers/ui/pages/tools/tools.js b/openbis_ng_ui/src/js/store/reducers/ui/pages/tools/tools.js new file mode 100644 index 0000000000000000000000000000000000000000..ffb16deaa11e3a0804373bcf052c157580f07ac3 --- /dev/null +++ b/openbis_ng_ui/src/js/store/reducers/ui/pages/tools/tools.js @@ -0,0 +1,14 @@ +import { combineReducers } from 'redux' +import pages from '@src/js/common/consts/pages.js' +import page from '@src/js/store/reducers/ui/pages/common/page.js' + +export default function types(state = {}, action) { + if (page.isPageAction(pages.TOOLS, action)) { + return combineReducers({ + currentRoute: page.currentRoute, + openTabs: page.openTabs + })(state, action) + } else { + return state + } +} diff --git a/openbis_ng_ui/srcTest/js/components/AppComponentLogin.test.js b/openbis_ng_ui/srcTest/js/components/AppComponentLogin.test.js index 380b74f6c32f03216d95703e3ff595465ab7c8b9..b480cda823b001765a8f0d42845207f303400a19 100644 --- a/openbis_ng_ui/srcTest/js/components/AppComponentLogin.test.js +++ b/openbis_ng_ui/srcTest/js/components/AppComponentLogin.test.js @@ -46,6 +46,10 @@ async function testLogin() { { label: 'Users', selected: false + }, + { + label: 'Tools', + selected: false } ] }, diff --git a/openbis_ng_ui/srcTest/js/components/AppComponentTest.js b/openbis_ng_ui/srcTest/js/components/AppComponentTest.js index ea692cf30a149542938cfa9e57ceeb82d2b99536..852dc0e60abc0b72e49cadbcb013349ca6fec7fd 100644 --- a/openbis_ng_ui/srcTest/js/components/AppComponentTest.js +++ b/openbis_ng_ui/srcTest/js/components/AppComponentTest.js @@ -25,6 +25,7 @@ export default class AppComponentTest extends ComponentTest { openbis.mockSearchVocabularies([]) openbis.mockSearchPersons([]) openbis.mockSearchGroups([]) + openbis.mockSearchPlugins([]) } async login(app) { diff --git a/openbis_ng_ui/srcTest/js/components/common/form/wrapper/SourceCodeFieldWrapper.js b/openbis_ng_ui/srcTest/js/components/common/form/wrapper/SourceCodeFieldWrapper.js new file mode 100644 index 0000000000000000000000000000000000000000..a1ec5ea269aada10cad4715aceef84908e540e64 --- /dev/null +++ b/openbis_ng_ui/srcTest/js/components/common/form/wrapper/SourceCodeFieldWrapper.js @@ -0,0 +1,13 @@ +import FieldWrapper from './FieldWrapper.js' + +export default class SourceCodeFieldWrapper extends FieldWrapper { + getFocused() { + if (this.getMode() === 'edit') { + return ( + document.activeElement === this.wrapper.find('textarea').getDOMNode() + ) + } else { + return false + } + } +} diff --git a/openbis_ng_ui/srcTest/js/components/tools/browser/ToolBrowserComponentFilter.test.js b/openbis_ng_ui/srcTest/js/components/tools/browser/ToolBrowserComponentFilter.test.js new file mode 100644 index 0000000000000000000000000000000000000000..5c413609eb122ed9188c5e5682d85a93feedb47a --- /dev/null +++ b/openbis_ng_ui/srcTest/js/components/tools/browser/ToolBrowserComponentFilter.test.js @@ -0,0 +1,63 @@ +import ToolBrowserComponentTest from '@srcTest/js/components/tools/browser/ToolBrowserComponentTest.js' +import ToolBrowserTestData from '@srcTest/js/components/tools/browser/ToolBrowserTestData.js' +import openbis from '@srcTest/js/services/openbis.js' + +let common = null + +beforeEach(() => { + common = new ToolBrowserComponentTest() + common.beforeEach() +}) + +describe(ToolBrowserComponentTest.SUITE, () => { + test('filter', testFilter) +}) + +async function testFilter() { + const { + testDynamicPropertyJythonPlugin, + testDynamicPropertyPredeployedPlugin, + testManagedPropertyJythonPlugin, + testEntityValidationJythonPlugin + } = ToolBrowserTestData + + openbis.mockSearchPlugins([ + testDynamicPropertyJythonPlugin, + testDynamicPropertyPredeployedPlugin, + testManagedPropertyJythonPlugin, + testEntityValidationJythonPlugin + ]) + + const browser = await common.mount() + + browser + .getFilter() + .change(testEntityValidationJythonPlugin.name.toUpperCase()) + await browser.update() + + browser.expectJSON({ + filter: { + value: testEntityValidationJythonPlugin.name.toUpperCase() + }, + nodes: [ + { level: 0, text: 'Entity Validation Plugins' }, + { level: 1, text: testEntityValidationJythonPlugin.name } + ] + }) + + browser.getFilter().getClearIcon().click() + await browser.update() + + browser.expectJSON({ + filter: { + value: null + }, + nodes: [ + { level: 0, text: 'Dynamic Property Plugins' }, + { level: 1, text: testDynamicPropertyJythonPlugin.name }, + { level: 1, text: testDynamicPropertyPredeployedPlugin.name }, + { level: 0, text: 'Entity Validation Plugins' }, + { level: 1, text: testEntityValidationJythonPlugin.name } + ] + }) +} diff --git a/openbis_ng_ui/srcTest/js/components/tools/browser/ToolBrowserComponentLoad.test.js b/openbis_ng_ui/srcTest/js/components/tools/browser/ToolBrowserComponentLoad.test.js new file mode 100644 index 0000000000000000000000000000000000000000..ada84839f76c58c060f5d2ec73f81d220ba896a6 --- /dev/null +++ b/openbis_ng_ui/srcTest/js/components/tools/browser/ToolBrowserComponentLoad.test.js @@ -0,0 +1,29 @@ +import ToolBrowserComponentTest from '@srcTest/js/components/tools/browser/ToolBrowserComponentTest.js' +import openbis from '@srcTest/js/services/openbis.js' + +let common = null + +beforeEach(() => { + common = new ToolBrowserComponentTest() + common.beforeEach() +}) + +describe(ToolBrowserComponentTest.SUITE, () => { + test('load', testLoad) +}) + +async function testLoad() { + openbis.mockSearchPlugins([]) + + const browser = await common.mount() + + browser.expectJSON({ + filter: { + value: null + }, + nodes: [ + { level: 0, text: 'Dynamic Property Plugins' }, + { level: 0, text: 'Entity Validation Plugins' } + ] + }) +} diff --git a/openbis_ng_ui/srcTest/js/components/tools/browser/ToolBrowserComponentOpenClose.test.js b/openbis_ng_ui/srcTest/js/components/tools/browser/ToolBrowserComponentOpenClose.test.js new file mode 100644 index 0000000000000000000000000000000000000000..ead4b607a7f74cfc00f856881de029b2d266151c --- /dev/null +++ b/openbis_ng_ui/srcTest/js/components/tools/browser/ToolBrowserComponentOpenClose.test.js @@ -0,0 +1,90 @@ +import ToolBrowserComponentTest from '@srcTest/js/components/tools/browser/ToolBrowserComponentTest.js' +import ToolBrowserTestData from '@srcTest/js/components/tools/browser/ToolBrowserTestData.js' +import openbis from '@srcTest/js/services/openbis.js' + +let common = null + +beforeEach(() => { + common = new ToolBrowserComponentTest() + common.beforeEach() +}) + +describe(ToolBrowserComponentTest.SUITE, () => { + test('open/close', testOpenClose) +}) + +async function testOpenClose() { + const { + testDynamicPropertyJythonPlugin, + testDynamicPropertyPredeployedPlugin, + testManagedPropertyJythonPlugin, + testEntityValidationJythonPlugin + } = ToolBrowserTestData + + openbis.mockSearchPlugins([ + testDynamicPropertyJythonPlugin, + testDynamicPropertyPredeployedPlugin, + testManagedPropertyJythonPlugin, + testEntityValidationJythonPlugin + ]) + + const browser = await common.mount() + + browser.getNodes()[0].getIcon().click() + await browser.update() + + browser.expectJSON({ + filter: { + value: null + }, + nodes: [ + { level: 0, text: 'Dynamic Property Plugins' }, + { level: 1, text: testDynamicPropertyJythonPlugin.name }, + { level: 1, text: testDynamicPropertyPredeployedPlugin.name }, + { level: 0, text: 'Entity Validation Plugins' } + ] + }) + + browser.getNodes()[3].getIcon().click() + await browser.update() + + browser.expectJSON({ + filter: { + value: null + }, + nodes: [ + { level: 0, text: 'Dynamic Property Plugins' }, + { level: 1, text: testDynamicPropertyJythonPlugin.name }, + { level: 1, text: testDynamicPropertyPredeployedPlugin.name }, + { level: 0, text: 'Entity Validation Plugins' }, + { level: 1, text: testEntityValidationJythonPlugin.name } + ] + }) + + browser.getNodes()[0].getIcon().click() + await browser.update() + + browser.expectJSON({ + filter: { + value: null + }, + nodes: [ + { level: 0, text: 'Dynamic Property Plugins' }, + { level: 0, text: 'Entity Validation Plugins' }, + { level: 1, text: testEntityValidationJythonPlugin.name } + ] + }) + + browser.getNodes()[1].getIcon().click() + await browser.update() + + browser.expectJSON({ + filter: { + value: null + }, + nodes: [ + { level: 0, text: 'Dynamic Property Plugins' }, + { level: 0, text: 'Entity Validation Plugins' } + ] + }) +} diff --git a/openbis_ng_ui/srcTest/js/components/tools/browser/ToolBrowserComponentTest.js b/openbis_ng_ui/srcTest/js/components/tools/browser/ToolBrowserComponentTest.js new file mode 100644 index 0000000000000000000000000000000000000000..9ed62735c5f2578ef31fb8032a126ce6a0d50be7 --- /dev/null +++ b/openbis_ng_ui/srcTest/js/components/tools/browser/ToolBrowserComponentTest.js @@ -0,0 +1,15 @@ +import React from 'react' +import ComponentTest from '@srcTest/js/components/common/ComponentTest.js' +import BrowserWrapper from '@srcTest/js/components/common/browser/wrapper/BrowserWrapper.js' +import ToolBrowser from '@src/js/components/tools/browser/ToolBrowser.jsx' + +export default class ToolBrowserComponentTest extends ComponentTest { + static SUITE = 'ToolBrowserComponent' + + constructor() { + super( + () => <ToolBrowser />, + wrapper => new BrowserWrapper(wrapper) + ) + } +} diff --git a/openbis_ng_ui/srcTest/js/components/tools/browser/ToolBrowserTestData.js b/openbis_ng_ui/srcTest/js/components/tools/browser/ToolBrowserTestData.js new file mode 100644 index 0000000000000000000000000000000000000000..9d477ad6d80582aba63b1c5edf75f082757db597 --- /dev/null +++ b/openbis_ng_ui/srcTest/js/components/tools/browser/ToolBrowserTestData.js @@ -0,0 +1,40 @@ +import openbis from '@srcTest/js/services/openbis.js' + +const testDynamicPropertyJythonPlugin = new openbis.Plugin() +testDynamicPropertyJythonPlugin.setName('TEST_DYNAMIC_PROPERTY_JYTHON') +testDynamicPropertyJythonPlugin.setPluginKind(openbis.PluginKind.JYTHON) +testDynamicPropertyJythonPlugin.setPluginType( + openbis.PluginType.DYNAMIC_PROPERTY +) + +const testDynamicPropertyPredeployedPlugin = new openbis.Plugin() +testDynamicPropertyPredeployedPlugin.setName( + 'TEST_DYNAMIC_PROPERTY_PREDEPLOYED' +) +testDynamicPropertyPredeployedPlugin.setPluginKind( + openbis.PluginKind.PREDEPLOYED +) +testDynamicPropertyPredeployedPlugin.setPluginType( + openbis.PluginType.DYNAMIC_PROPERTY +) + +const testManagedPropertyJythonPlugin = new openbis.Plugin() +testManagedPropertyJythonPlugin.setName('TEST_MANAGED_PROPERTY_JYTHON') +testManagedPropertyJythonPlugin.setPluginKind(openbis.PluginKind.JYTHON) +testManagedPropertyJythonPlugin.setPluginType( + openbis.PluginType.MANAGED_PROPERTY +) + +const testEntityValidationJythonPlugin = new openbis.Plugin() +testEntityValidationJythonPlugin.setName('TEST_ENTITY_VALIDATION_JYTHON') +testEntityValidationJythonPlugin.setPluginKind(openbis.PluginKind.JYTHON) +testEntityValidationJythonPlugin.setPluginType( + openbis.PluginType.ENTITY_VALIDATION +) + +export default { + testDynamicPropertyJythonPlugin, + testDynamicPropertyPredeployedPlugin, + testManagedPropertyJythonPlugin, + testEntityValidationJythonPlugin +} diff --git a/openbis_ng_ui/srcTest/js/components/tools/form/plugin/PluginFormComponentChange.test.js b/openbis_ng_ui/srcTest/js/components/tools/form/plugin/PluginFormComponentChange.test.js new file mode 100644 index 0000000000000000000000000000000000000000..c38765746f639bbcd0921f87cb53cc7b98138f8e --- /dev/null +++ b/openbis_ng_ui/srcTest/js/components/tools/form/plugin/PluginFormComponentChange.test.js @@ -0,0 +1,88 @@ +import PluginFormComponentTest from '@srcTest/js/components/tools/form/plugin/PluginFormComponentTest.js' +import PluginFormTestData from '@srcTest/js/components/tools/form/plugin/PluginFormTestData.js' + +let common = null + +beforeEach(() => { + common = new PluginFormComponentTest() + common.beforeEach() +}) + +describe(PluginFormComponentTest.SUITE, () => { + test('change DYNAMIC_PROPERTY', async () => { + const { testDynamicPropertyJythonPlugin } = PluginFormTestData + await testChange(testDynamicPropertyJythonPlugin) + }) + test('change ENTITY_VALIDATION', async () => { + const { testEntityValidationJythonPlugin } = PluginFormTestData + await testChange(testEntityValidationJythonPlugin) + }) +}) + +async function testChange(plugin) { + const form = await common.mountExisting(plugin) + + form.getButtons().getEdit().click() + await form.update() + + form.getScript().getScript().change('updated script') + await form.update() + + form.getParameters().getDescription().change('updated description') + await form.update() + + form.expectJSON({ + script: { + title: 'Script', + script: { + label: 'Script', + value: 'updated script', + enabled: true, + mode: 'edit' + } + }, + parameters: { + title: 'Plugin', + name: { + label: 'Name', + value: plugin.getName(), + enabled: false, + mode: 'edit' + }, + entityKind: { + label: 'Entity Kind', + value: + plugin.getEntityKinds().length === 1 + ? plugin.getEntityKinds()[0] + : null, + options: [ + { label: 'MATERIAL' }, + { label: 'EXPERIMENT' }, + { label: 'SAMPLE' }, + { label: 'DATA_SET' } + ], + enabled: false, + mode: 'edit' + }, + description: { + label: 'Description', + value: 'updated description', + enabled: true, + mode: 'edit' + } + }, + buttons: { + save: { + enabled: true + }, + cancel: { + enabled: true + }, + edit: null, + message: { + text: 'You have unsaved changes.', + type: 'warning' + } + } + }) +} diff --git a/openbis_ng_ui/srcTest/js/components/tools/form/plugin/PluginFormComponentLoad.test.js b/openbis_ng_ui/srcTest/js/components/tools/form/plugin/PluginFormComponentLoad.test.js new file mode 100644 index 0000000000000000000000000000000000000000..91dde99066f1725f515351fd0ea3c5f6218102a3 --- /dev/null +++ b/openbis_ng_ui/srcTest/js/components/tools/form/plugin/PluginFormComponentLoad.test.js @@ -0,0 +1,243 @@ +import PluginFormComponentTest from '@srcTest/js/components/tools/form/plugin/PluginFormComponentTest.js' +import PluginFormTestData from '@srcTest/js/components/tools/form/plugin/PluginFormTestData.js' +import openbis from '@srcTest/js/services/openbis.js' + +let common = null + +beforeEach(() => { + common = new PluginFormComponentTest() + common.beforeEach() +}) + +describe(PluginFormComponentTest.SUITE, () => { + test('load new DYNAMIC_PROPERTY', async () => { + await testLoadNew(openbis.PluginType.DYNAMIC_PROPERTY) + }) + test('load new ENTITY_VALIDATION', async () => { + await testLoadNew(openbis.PluginType.ENTITY_VALIDATION) + }) + test('load existing DYNAMIC_PROPERTY JYTHON', async () => { + const { testDynamicPropertyJythonPlugin } = PluginFormTestData + await testLoadExistingJython(testDynamicPropertyJythonPlugin) + }) + test('load existing ENTITY_VALIDATION JYTHON', async () => { + const { testEntityValidationJythonPlugin } = PluginFormTestData + await testLoadExistingJython(testEntityValidationJythonPlugin) + }) + test('load existing DYNAMIC_PROPERTY PREDEPLOYED', async () => { + const { testDynamicPropertyPredeployedPlugin } = PluginFormTestData + await testLoadExistingPredeployed(testDynamicPropertyPredeployedPlugin) + }) + test('load existing ENTITY_VALIDATION PREDEPLOYED', async () => { + const { testEntityValidationPredeployedPlugin } = PluginFormTestData + await testLoadExistingPredeployed(testEntityValidationPredeployedPlugin) + }) +}) + +async function testLoadNew(pluginType) { + const form = await common.mountNew(pluginType) + + form.expectJSON({ + script: { + title: 'Script', + script: { + label: 'Script', + value: null, + enabled: true, + mode: 'edit' + } + }, + parameters: { + title: 'Plugin', + name: { + label: 'Name', + value: null, + enabled: true, + mode: 'edit' + }, + entityKind: { + label: 'Entity Kind', + value: null, + options: [ + { label: 'MATERIAL' }, + { label: 'EXPERIMENT' }, + { label: 'SAMPLE' }, + { label: 'DATA_SET' } + ], + enabled: true, + mode: 'edit' + }, + description: { + label: 'Description', + value: null, + enabled: true, + mode: 'edit' + } + }, + buttons: { + save: { + enabled: true + }, + edit: null, + cancel: null, + message: null + } + }) +} + +async function testLoadExistingJython(plugin) { + const form = await common.mountExisting(plugin) + + form.expectJSON({ + script: { + title: 'Script', + script: { + label: 'Script', + value: plugin.script, + mode: 'view' + } + }, + parameters: { + title: 'Plugin', + name: { + label: 'Name', + value: plugin.getName(), + mode: 'view' + }, + entityKind: { + label: 'Entity Kind', + value: + plugin.getEntityKinds().length === 1 + ? plugin.getEntityKinds()[0] + : null, + options: [ + { label: 'MATERIAL' }, + { label: 'EXPERIMENT' }, + { label: 'SAMPLE' }, + { label: 'DATA_SET' } + ], + mode: 'view' + }, + description: { + label: 'Description', + value: plugin.getDescription(), + mode: 'view' + } + }, + buttons: { + edit: { + enabled: true + }, + save: null, + cancel: null, + message: null + } + }) + + form.getButtons().getEdit().click() + await form.update() + + form.expectJSON({ + script: { + title: 'Script', + script: { + label: 'Script', + value: plugin.script, + enabled: true, + mode: 'edit' + } + }, + parameters: { + title: 'Plugin', + name: { + label: 'Name', + value: plugin.getName(), + enabled: false, + mode: 'edit' + }, + entityKind: { + label: 'Entity Kind', + value: + plugin.getEntityKinds().length === 1 + ? plugin.getEntityKinds()[0] + : null, + options: [ + { label: 'MATERIAL' }, + { label: 'EXPERIMENT' }, + { label: 'SAMPLE' }, + { label: 'DATA_SET' } + ], + enabled: false, + mode: 'edit' + }, + description: { + label: 'Description', + value: plugin.getDescription(), + enabled: true, + mode: 'edit' + } + }, + buttons: { + save: { + enabled: true + }, + cancel: { + enabled: true + }, + edit: null, + message: null + } + }) +} + +async function testLoadExistingPredeployed(plugin) { + const form = await common.mountExisting(plugin) + + form.expectJSON({ + script: null, + parameters: { + title: 'Plugin', + messages: [ + { + text: 'The plugin is disabled.', + type: 'warning' + }, + { + text: + 'This is a predeployed Java plugin. Its parameters and logic are defined in the plugin Java class and therefore cannot be changed from the UI.', + type: 'info' + } + ], + name: { + label: 'Name', + value: plugin.getName(), + mode: 'view' + }, + entityKind: { + label: 'Entity Kind', + value: + plugin.getEntityKinds().length === 1 + ? plugin.getEntityKinds()[0] + : null, + options: [ + { label: 'MATERIAL' }, + { label: 'EXPERIMENT' }, + { label: 'SAMPLE' }, + { label: 'DATA_SET' } + ], + mode: 'view' + }, + description: { + label: 'Description', + value: plugin.getDescription(), + mode: 'view' + } + }, + buttons: { + edit: null, + save: null, + cancel: null, + message: null + } + }) +} diff --git a/openbis_ng_ui/srcTest/js/components/tools/form/plugin/PluginFormComponentSave.test.js b/openbis_ng_ui/srcTest/js/components/tools/form/plugin/PluginFormComponentSave.test.js new file mode 100644 index 0000000000000000000000000000000000000000..f0f2ff8f4e28c6452cb0fc59148ef599950b48ed --- /dev/null +++ b/openbis_ng_ui/srcTest/js/components/tools/form/plugin/PluginFormComponentSave.test.js @@ -0,0 +1,113 @@ +import PluginFormComponentTest from '@srcTest/js/components/tools/form/plugin/PluginFormComponentTest.js' +import PluginFormTestData from '@srcTest/js/components/tools/form/plugin/PluginFormTestData.js' +import openbis from '@srcTest/js/services/openbis.js' + +let common = null + +beforeEach(() => { + common = new PluginFormComponentTest() + common.beforeEach() +}) + +describe(PluginFormComponentTest.SUITE, () => { + test('save create DYNAMIC_PROPERTY', async () => { + await testSaveCreate(openbis.PluginType.DYNAMIC_PROPERTY) + }) + test('save create ENTITY_VALIDATION', async () => { + await testSaveCreate(openbis.PluginType.ENTITY_VALIDATION) + }) + test('save update DYNAMIC_PROPERTY', async () => { + const { testDynamicPropertyJythonPlugin } = PluginFormTestData + await testSaveUpdate(testDynamicPropertyJythonPlugin) + }) + test('save update ENTITY_VALIDATION', async () => { + const { testEntityValidationJythonPlugin } = PluginFormTestData + await testSaveUpdate(testEntityValidationJythonPlugin) + }) +}) + +async function testSaveCreate(pluginType) { + const form = await common.mountNew(pluginType) + + form.getParameters().getName().change('test-plugin') + await form.update() + + form.getParameters().getEntityKind().change(openbis.EntityKind.SAMPLE) + await form.update() + + form.getParameters().getDescription().change('test description') + await form.update() + + form.getScript().getScript().change('test script') + await form.update() + + form.getButtons().getSave().click() + await form.update() + + expectExecuteOperations([ + createPluginOperation({ + pluginType, + name: 'test-plugin', + entityKind: openbis.EntityKind.SAMPLE, + description: 'test description', + script: 'test script' + }) + ]) +} + +async function testSaveUpdate(plugin) { + const form = await common.mountExisting(plugin) + + form.getButtons().getEdit().click() + await form.update() + + form.getParameters().getDescription().change('updated description') + await form.update() + + form.getScript().getScript().change('updated script') + await form.update() + + form.getButtons().getSave().click() + await form.update() + + expectExecuteOperations([ + updatePluginOperation({ + name: plugin.getName(), + description: 'updated description', + script: 'updated script' + }) + ]) +} + +function createPluginOperation({ + pluginType, + name, + entityKind, + description, + script +}) { + const creation = new openbis.PluginCreation() + creation.setPluginType(pluginType) + creation.setEntityKind(entityKind) + creation.setName(name) + creation.setDescription(description) + creation.setScript(script) + return new openbis.CreatePluginsOperation([creation]) +} + +function updatePluginOperation({ name, description, script }) { + const update = new openbis.PluginUpdate() + update.setPluginId(new openbis.PluginPermId(name)) + update.setDescription(description) + update.setScript(script) + return new openbis.UpdatePluginsOperation([update]) +} + +function expectExecuteOperations(expectedOperations) { + expect(common.facade.executeOperations).toHaveBeenCalledTimes(1) + const actualOperations = common.facade.executeOperations.mock.calls[0][0] + expect(actualOperations.length).toEqual(expectedOperations.length) + actualOperations.forEach((actualOperation, index) => { + expect(actualOperation).toMatchObject(expectedOperations[index]) + }) +} diff --git a/openbis_ng_ui/srcTest/js/components/tools/form/plugin/PluginFormComponentTest.js b/openbis_ng_ui/srcTest/js/components/tools/form/plugin/PluginFormComponentTest.js new file mode 100644 index 0000000000000000000000000000000000000000..bc4c8756bc4f5e2a6f61091a2ffe0fb795ea4982 --- /dev/null +++ b/openbis_ng_ui/srcTest/js/components/tools/form/plugin/PluginFormComponentTest.js @@ -0,0 +1,62 @@ +import React from 'react' +import ComponentTest from '@srcTest/js/components/common/ComponentTest.js' +import PluginForm from '@src/js/components/tools/form/plugin/PluginForm.jsx' +import PluginFormWrapper from '@srcTest/js/components/tools/form/plugin/wrapper/PluginFormWrapper.js' +import PluginFormController from '@src/js/components/tools/form/plugin/PluginFormController.js' +import PluginFormFacade from '@src/js/components/tools/form/plugin/PluginFormFacade' +import objectTypes from '@src/js/common/consts/objectType.js' +import openbis from '@srcTest/js/services/openbis.js' + +jest.mock('@src/js/components/tools/form/plugin/PluginFormFacade') + +export default class PluginFormComponentTest extends ComponentTest { + static SUITE = 'PluginFormComponent' + + constructor() { + super( + object => <PluginForm object={object} controller={this.controller} />, + wrapper => new PluginFormWrapper(wrapper) + ) + this.facade = null + this.controller = null + } + + async beforeEach() { + super.beforeEach() + + this.facade = new PluginFormFacade() + this.controller = new PluginFormController(this.facade) + } + + async mountNew(pluginType) { + if (pluginType === openbis.PluginType.DYNAMIC_PROPERTY) { + return await this.mount({ + type: objectTypes.NEW_DYNAMIC_PROPERTY_PLUGIN + }) + } else if (pluginType === openbis.PluginType.ENTITY_VALIDATION) { + return await this.mount({ + type: objectTypes.NEW_ENTITY_VALIDATION_PLUGIN + }) + } else { + throw Error('Unsupported plugin type: ' + pluginType) + } + } + + async mountExisting(plugin) { + this.facade.loadPlugin.mockReturnValue(Promise.resolve(plugin)) + + if (plugin.pluginType === openbis.PluginType.DYNAMIC_PROPERTY) { + return await this.mount({ + id: plugin.getName(), + type: objectTypes.DYNAMIC_PROPERTY_PLUGIN + }) + } else if (plugin.pluginType === openbis.PluginType.ENTITY_VALIDATION) { + return await this.mount({ + id: plugin.getName(), + type: objectTypes.ENTITY_VALIDATION_PLUGIN + }) + } else { + throw Error('Unsupported plugin type: ' + plugin.pluginType) + } + } +} diff --git a/openbis_ng_ui/srcTest/js/components/tools/form/plugin/PluginFormComponentValidate.test.js b/openbis_ng_ui/srcTest/js/components/tools/form/plugin/PluginFormComponentValidate.test.js new file mode 100644 index 0000000000000000000000000000000000000000..825d2c77c60d917cbf8babe476993fa51bdeddb9 --- /dev/null +++ b/openbis_ng_ui/srcTest/js/components/tools/form/plugin/PluginFormComponentValidate.test.js @@ -0,0 +1,68 @@ +import PluginFormComponentTest from '@srcTest/js/components/tools/form/plugin/PluginFormComponentTest.js' +import openbis from '@srcTest/js/services/openbis.js' + +let common = null + +beforeEach(() => { + common = new PluginFormComponentTest() + common.beforeEach() +}) + +describe(PluginFormComponentTest.SUITE, () => { + test('validate DYNAMIC_PROPERTY', async () => { + await testValidate(openbis.PluginType.DYNAMIC_PROPERTY) + }) + test('validate ENTITY_VALIDATION', async () => { + await testValidate(openbis.PluginType.ENTITY_VALIDATION) + }) +}) + +async function testValidate(pluginType) { + const form = await common.mountNew(pluginType) + + form.getButtons().getSave().click() + await form.update() + + form.expectJSON({ + script: { + title: 'Script', + script: { + label: 'Script', + value: null, + error: 'Script cannot be empty', + enabled: true, + mode: 'edit' + } + }, + parameters: { + title: 'Plugin', + name: { + label: 'Name', + value: null, + error: 'Name cannot be empty', + enabled: true, + mode: 'edit' + }, + entityKind: { + label: 'Entity Kind', + value: null, + enabled: true, + mode: 'edit' + }, + description: { + label: 'Description', + value: null, + enabled: true, + mode: 'edit' + } + }, + buttons: { + save: { + enabled: true + }, + edit: null, + cancel: null, + message: null + } + }) +} diff --git a/openbis_ng_ui/srcTest/js/components/tools/form/plugin/PluginFormTestData.js b/openbis_ng_ui/srcTest/js/components/tools/form/plugin/PluginFormTestData.js new file mode 100644 index 0000000000000000000000000000000000000000..5b28a31b8fd980999d283ff2b725f032022a6973 --- /dev/null +++ b/openbis_ng_ui/srcTest/js/components/tools/form/plugin/PluginFormTestData.js @@ -0,0 +1,69 @@ +import openbis from '@srcTest/js/services/openbis.js' + +const testDynamicPropertyJythonPlugin = new openbis.Plugin() +testDynamicPropertyJythonPlugin.setName('TEST_DYNAMIC_PROPERTY_JYTHON') +testDynamicPropertyJythonPlugin.setPluginKind(openbis.PluginKind.JYTHON) +testDynamicPropertyJythonPlugin.setPluginType( + openbis.PluginType.DYNAMIC_PROPERTY +) +testDynamicPropertyJythonPlugin.setEntityKinds([openbis.EntityKind.SAMPLE]) +testDynamicPropertyJythonPlugin.setDescription( + 'Description of TEST_DYNAMIC_PROPERTY_JYTHON' +) +testDynamicPropertyJythonPlugin.setScript('def calculate():\n return "abc"') + +const testDynamicPropertyPredeployedPlugin = new openbis.Plugin() +testDynamicPropertyPredeployedPlugin.setName( + 'TEST_DYNAMIC_PROPERTY_PREDEPLOYED' +) +testDynamicPropertyPredeployedPlugin.setPluginKind( + openbis.PluginKind.PREDEPLOYED +) +testDynamicPropertyPredeployedPlugin.setPluginType( + openbis.PluginType.DYNAMIC_PROPERTY +) +testDynamicPropertyPredeployedPlugin.setEntityKinds([ + openbis.EntityKind.EXPERIMENT +]) +testDynamicPropertyPredeployedPlugin.setDescription( + 'Description of TEST_DYNAMIC_PROPERTY_PREDEPLOYED' +) + +const testEntityValidationJythonPlugin = new openbis.Plugin() +testEntityValidationJythonPlugin.setName('TEST_ENTITY_VALIDATION_JYTHON') +testEntityValidationJythonPlugin.setPluginKind(openbis.PluginKind.JYTHON) +testEntityValidationJythonPlugin.setPluginType( + openbis.PluginType.ENTITY_VALIDATION +) +testEntityValidationJythonPlugin.setEntityKinds([openbis.EntityKind.DATA_SET]) +testEntityValidationJythonPlugin.setDescription( + 'Description of TEST_ENTITY_VALIDATION_JYTHON' +) +testEntityValidationJythonPlugin.setScript('def validate():\n return True') + +const testEntityValidationPredeployedPlugin = new openbis.Plugin() +testEntityValidationPredeployedPlugin.setName( + 'TEST_ENTITY_VALIDATION_PREDEPLOYED' +) +testEntityValidationPredeployedPlugin.setPluginKind( + openbis.PluginKind.PREDEPLOYED +) +testEntityValidationPredeployedPlugin.setPluginType( + openbis.PluginType.ENTITY_VALIDATION +) +testEntityValidationPredeployedPlugin.setEntityKinds([ + openbis.EntityKind.SAMPLE, + openbis.EntityKind.EXPERIMENT, + openbis.EntityKind.DATA_SET, + openbis.EntityKind.MATERIAL +]) +testEntityValidationPredeployedPlugin.setDescription( + 'Description of TEST_ENTITY_VALIDATION_PREDEPLOYED' +) + +export default { + testDynamicPropertyJythonPlugin, + testDynamicPropertyPredeployedPlugin, + testEntityValidationJythonPlugin, + testEntityValidationPredeployedPlugin +} diff --git a/openbis_ng_ui/srcTest/js/components/tools/form/plugin/wrapper/PluginFormButtonsWrapper.js b/openbis_ng_ui/srcTest/js/components/tools/form/plugin/wrapper/PluginFormButtonsWrapper.js new file mode 100644 index 0000000000000000000000000000000000000000..6c87b0778492bdb2f715c5c26525b4ea3f77cfb9 --- /dev/null +++ b/openbis_ng_ui/srcTest/js/components/tools/form/plugin/wrapper/PluginFormButtonsWrapper.js @@ -0,0 +1,3 @@ +import PageButtonsWrapper from '@srcTest/js/components/common/page/wrapper/PageButtonsWrapper.js' + +export default class PluginFormButtonsWrapper extends PageButtonsWrapper {} diff --git a/openbis_ng_ui/srcTest/js/components/tools/form/plugin/wrapper/PluginFormParametersWrapper.js b/openbis_ng_ui/srcTest/js/components/tools/form/plugin/wrapper/PluginFormParametersWrapper.js new file mode 100644 index 0000000000000000000000000000000000000000..fbfeca42bda384bcde31ead5f8e6b9e3a646ec54 --- /dev/null +++ b/openbis_ng_ui/srcTest/js/components/tools/form/plugin/wrapper/PluginFormParametersWrapper.js @@ -0,0 +1,34 @@ +import TextField from '@src/js/components/common/form/TextField.jsx' +import TextFieldWrapper from '@srcTest/js/components/common/form/wrapper/TextFieldWrapper.js' +import SelectField from '@src/js/components/common/form/SelectField.jsx' +import SelectFieldWrapper from '@srcTest/js/components/common/form/wrapper/SelectFieldWrapper.js' +import PageParametersPanelWrapper from '@srcTest/js/components/common/page/wrapper/PageParametersPanelWrapper.js' + +export default class PluginFormParametersWrapper extends PageParametersPanelWrapper { + getName() { + return new TextFieldWrapper( + this.findComponent(TextField).filter({ name: 'name' }) + ) + } + + getEntityKind() { + return new SelectFieldWrapper( + this.findComponent(SelectField).filter({ name: 'entityKind' }) + ) + } + + getDescription() { + return new TextFieldWrapper( + this.findComponent(TextField).filter({ name: 'description' }) + ) + } + + toJSON() { + return { + ...super.toJSON(), + name: this.getName().toJSON(), + entityKind: this.getEntityKind().toJSON(), + description: this.getDescription().toJSON() + } + } +} diff --git a/openbis_ng_ui/srcTest/js/components/tools/form/plugin/wrapper/PluginFormScriptWrapper.js b/openbis_ng_ui/srcTest/js/components/tools/form/plugin/wrapper/PluginFormScriptWrapper.js new file mode 100644 index 0000000000000000000000000000000000000000..e81f186fefce3d5cd5ecb7e410ef84d1f62f253c --- /dev/null +++ b/openbis_ng_ui/srcTest/js/components/tools/form/plugin/wrapper/PluginFormScriptWrapper.js @@ -0,0 +1,25 @@ +import BaseWrapper from '@srcTest/js/components/common/wrapper/BaseWrapper.js' +import Header from '@src/js/components/common/form/Header.jsx' +import SourceCodeField from '@src/js/components/common/form/SourceCodeField.jsx' +import SourceCodeFieldWrapper from '@srcTest/js/components/common/form/wrapper/SourceCodeFieldWrapper.js' + +export default class PluginFormScriptWrapper extends BaseWrapper { + getTitle() { + return this.findComponent(Header) + } + + getScript() { + return new SourceCodeFieldWrapper(this.findComponent(SourceCodeField)) + } + + toJSON() { + if (this.wrapper.exists()) { + return { + title: this.getTitle().exists() ? this.getTitle().text() : null, + script: this.getScript().toJSON() + } + } else { + return null + } + } +} diff --git a/openbis_ng_ui/srcTest/js/components/tools/form/plugin/wrapper/PluginFormWrapper.js b/openbis_ng_ui/srcTest/js/components/tools/form/plugin/wrapper/PluginFormWrapper.js new file mode 100644 index 0000000000000000000000000000000000000000..74f2dc9e7eedcbf283cb7a2311dcb701e3ea493f --- /dev/null +++ b/openbis_ng_ui/srcTest/js/components/tools/form/plugin/wrapper/PluginFormWrapper.js @@ -0,0 +1,31 @@ +import BaseWrapper from '@srcTest/js/components/common/wrapper/BaseWrapper.js' +import PluginFormScript from '@src/js/components/tools/form/plugin/PluginFormScript.jsx' +import PluginFormScriptWrapper from '@srcTest/js/components/tools/form/plugin/wrapper/PluginFormScriptWrapper.js' +import PluginFormParameters from '@src/js/components/tools/form/plugin/PluginFormParameters.jsx' +import PluginFormParametersWrapper from '@srcTest/js/components/tools/form/plugin/wrapper/PluginFormParametersWrapper.js' +import PluginFormButtons from '@src/js/components/tools/form/plugin/PluginFormButtons.jsx' +import PluginFormButtonsWrapper from '@srcTest/js/components/tools/form/plugin/wrapper/PluginFormButtonsWrapper.js' + +export default class PluginFormWrapper extends BaseWrapper { + getScript() { + return new PluginFormScriptWrapper(this.findComponent(PluginFormScript)) + } + + getParameters() { + return new PluginFormParametersWrapper( + this.findComponent(PluginFormParameters) + ) + } + + getButtons() { + return new PluginFormButtonsWrapper(this.findComponent(PluginFormButtons)) + } + + toJSON() { + return { + script: this.getScript().toJSON(), + parameters: this.getParameters().toJSON(), + buttons: this.getButtons().toJSON() + } + } +} diff --git a/openbis_ng_ui/srcTest/js/mockStyles.js b/openbis_ng_ui/srcTest/js/mockStyles.js new file mode 100644 index 0000000000000000000000000000000000000000..2c6a41afe332fcb44ff59d34f1841231a50ff084 --- /dev/null +++ b/openbis_ng_ui/srcTest/js/mockStyles.js @@ -0,0 +1 @@ +// styles are not needed for the tests diff --git a/openbis_ng_ui/srcTest/js/services/openbis/api.js b/openbis_ng_ui/srcTest/js/services/openbis/api.js index f33f891bb32d77e7a5203dad3a3da7d51e1ceeea..55c2ad6922933a42c6413effc941bab5bb68b193 100644 --- a/openbis_ng_ui/srcTest/js/services/openbis/api.js +++ b/openbis_ng_ui/srcTest/js/services/openbis/api.js @@ -15,6 +15,7 @@ const getPersons = jest.fn() const getPropertyTypes = jest.fn() const getSampleTypes = jest.fn() const getVocabularies = jest.fn() +const getPlugins = jest.fn() const searchAuthorizationGroups = jest.fn() const searchDataSetTypes = jest.fn() const searchExperimentTypes = jest.fn() @@ -85,6 +86,12 @@ const mockSearchVocabularies = vocabularies => { searchVocabularies.mockReturnValue(Promise.resolve(searchResult)) } +const mockSearchPlugins = plugins => { + const searchResult = new dto.SearchResult() + searchResult.setObjects(plugins) + searchPlugins.mockReturnValue(Promise.resolve(searchResult)) +} + export default { login, logout, @@ -101,6 +108,7 @@ export default { getPropertyTypes, getSampleTypes, getVocabularies, + getPlugins, searchAuthorizationGroups, searchDataSetTypes, searchExperimentTypes, @@ -127,5 +135,6 @@ export default { mockSearchPersons, mockSearchSampleTypes, mockSearchPropertyTypes, - mockSearchVocabularies + mockSearchVocabularies, + mockSearchPlugins } diff --git a/openbis_ng_ui/srcTest/js/services/openbis/dto.js b/openbis_ng_ui/srcTest/js/services/openbis/dto.js index 904b252629c4395b60da553ca24b4758a49c9213..44b258a2019628c0d5c65289f08e7658590ede04 100644 --- a/openbis_ng_ui/srcTest/js/services/openbis/dto.js +++ b/openbis_ng_ui/srcTest/js/services/openbis/dto.js @@ -10,6 +10,7 @@ import CreateDataSetTypesOperation from 'as/dto/dataset/create/CreateDataSetType import CreateExperimentTypesOperation from 'as/dto/experiment/create/CreateExperimentTypesOperation' import CreateMaterialTypesOperation from 'as/dto/material/create/CreateMaterialTypesOperation' import CreatePersonsOperation from 'as/dto/person/create/CreatePersonsOperation' +import CreatePluginsOperation from 'as/dto/plugin/create/CreatePluginsOperation' import CreatePropertyTypesOperation from 'as/dto/property/create/CreatePropertyTypesOperation' import CreateRoleAssignmentsOperation from 'as/dto/roleassignment/create/CreateRoleAssignmentsOperation' import CreateSampleTypesOperation from 'as/dto/sample/create/CreateSampleTypesOperation' @@ -30,6 +31,7 @@ import DeleteAuthorizationGroupsOperation from 'as/dto/authorizationgroup/delete import DeleteDataSetTypesOperation from 'as/dto/dataset/delete/DeleteDataSetTypesOperation' import DeleteExperimentTypesOperation from 'as/dto/experiment/delete/DeleteExperimentTypesOperation' import DeleteMaterialTypesOperation from 'as/dto/material/delete/DeleteMaterialTypesOperation' +import DeletePluginsOperation from 'as/dto/plugin/delete/DeletePluginsOperation' import DeletePropertyTypesOperation from 'as/dto/property/delete/DeletePropertyTypesOperation' import DeleteRoleAssignmentsOperation from 'as/dto/roleassignment/delete/DeleteRoleAssignmentsOperation' import DeleteSampleTypesOperation from 'as/dto/sample/delete/DeleteSampleTypesOperation' @@ -60,7 +62,11 @@ import PersonPermId from 'as/dto/person/id/PersonPermId' import PersonSearchCriteria from 'as/dto/person/search/PersonSearchCriteria' import PersonUpdate from 'as/dto/person/update/PersonUpdate' import Plugin from 'as/dto/plugin/Plugin' +import PluginCreation from 'as/dto/plugin/create/PluginCreation' +import PluginUpdate from 'as/dto/plugin/update/PluginUpdate' +import PluginDeletionOptions from 'as/dto/plugin/delete/PluginDeletionOptions' import PluginFetchOptions from 'as/dto/plugin/fetchoptions/PluginFetchOptions' +import PluginKind from 'as/dto/plugin/PluginKind' import PluginPermId from 'as/dto/plugin/id/PluginPermId' import PluginSearchCriteria from 'as/dto/plugin/search/PluginSearchCriteria' import PluginType from 'as/dto/plugin/PluginType' @@ -109,6 +115,7 @@ import UpdateDataSetTypesOperation from 'as/dto/dataset/update/UpdateDataSetType import UpdateExperimentTypesOperation from 'as/dto/experiment/update/UpdateExperimentTypesOperation' import UpdateMaterialTypesOperation from 'as/dto/material/update/UpdateMaterialTypesOperation' import UpdatePersonsOperation from 'as/dto/person/update/UpdatePersonsOperation' +import UpdatePluginsOperation from 'as/dto/plugin/update/UpdatePluginsOperation' import UpdatePropertyTypesOperation from 'as/dto/property/update/UpdatePropertyTypesOperation' import UpdateSampleTypesOperation from 'as/dto/sample/update/UpdateSampleTypesOperation' import UpdateVocabulariesOperation from 'as/dto/vocabulary/update/UpdateVocabulariesOperation' @@ -142,6 +149,7 @@ const dto = { CreateExperimentTypesOperation, CreateMaterialTypesOperation, CreatePersonsOperation, + CreatePluginsOperation, CreatePropertyTypesOperation, CreateRoleAssignmentsOperation, CreateSampleTypesOperation, @@ -162,6 +170,7 @@ const dto = { DeleteDataSetTypesOperation, DeleteExperimentTypesOperation, DeleteMaterialTypesOperation, + DeletePluginsOperation, DeletePropertyTypesOperation, DeleteRoleAssignmentsOperation, DeleteSampleTypesOperation, @@ -192,7 +201,11 @@ const dto = { PersonSearchCriteria, PersonUpdate, Plugin, + PluginCreation, + PluginUpdate, + PluginDeletionOptions, PluginFetchOptions, + PluginKind, PluginPermId, PluginSearchCriteria, PluginType, @@ -241,6 +254,7 @@ const dto = { UpdateExperimentTypesOperation, UpdateMaterialTypesOperation, UpdatePersonsOperation, + UpdatePluginsOperation, UpdatePropertyTypesOperation, UpdateSampleTypesOperation, UpdateVocabulariesOperation,