allow adding relationship across schemas via manual relationship (fix #526) (#533)

Adds a schema dropdown to manual relationship section which allows creating manual relationship across schemas. This feature is added based on this PR #526 

- [x] Console

Adds a schema dropdown to manual relationship section which allows creating manual relationship across schemas
This commit is contained in:
Karthik Venkateswaran 2018-09-29 11:23:15 +05:30 committed by Shahidh K Muhammed
parent f847758edf
commit c98c88c00c
7 changed files with 348 additions and 296 deletions

View File

@ -25,6 +25,9 @@ const FETCH_SCHEMA_LIST = 'Data/FETCH_SCHEMA_LIST';
const UPDATE_CURRENT_SCHEMA = 'Data/UPDATE_CURRENT_SCHEMA';
const ACCESS_KEY_ERROR = 'Data/ACCESS_KEY_ERROR';
const UPDATE_DATA_HEADERS = 'Data/UPDATE_DATA_HEADERS';
const UPDATE_MANUAL_REL_TABLE_LIST = 'Data/UPDATE_MANUAL_REL_TABLE_LIST';
const RESET_MANUAL_REL_TABLE_LIST = 'Data/RESET_MANUAL_REL_TABLE_LIST';
const UPDATE_REMOTE_SCHEMA_MANUAL_REL = 'Data/UPDATE_SCHEMA_MANUAL_REL';
const MAKE_REQUEST = 'ModifyTable/MAKE_REQUEST';
const REQUEST_SUCCESS = 'ModifyTable/REQUEST_SUCCESS';
@ -303,6 +306,34 @@ const makeMigrationCall = (
);
};
const fetchTableListBySchema = schemaName => (dispatch, getState) => {
const url = Endpoints.getSchema;
const options = {
credentials: globalCookiePolicy,
method: 'POST',
headers: dataHeaders(getState),
body: JSON.stringify({
type: 'select',
args: {
table: {
name: 'hdb_table',
schema: 'hdb_catalog',
},
columns: ['*.*'],
where: { table_schema: schemaName },
},
}),
};
return dispatch(requestAction(url, options)).then(
data => {
dispatch({ type: UPDATE_MANUAL_REL_TABLE_LIST, data: data });
},
error => {
console.error('Failed to load table list' + JSON.stringify(error));
}
);
};
/* ******************************************************* */
const dataReducer = (state = defaultState, action) => {
// eslint-disable-line no-unused-vars
@ -374,6 +405,45 @@ const dataReducer = (state = defaultState, action) => {
return { ...state, accessKeyError: action.data };
case UPDATE_DATA_HEADERS:
return { ...state, dataHeaders: action.data };
case UPDATE_REMOTE_SCHEMA_MANUAL_REL:
return {
...state,
modify: {
...state.modify,
relAdd: {
...state.modify.relAdd,
manualRelInfo: {
...state.modify.relAdd.manualRelInfo,
remoteSchema: action.data,
},
},
},
};
case UPDATE_MANUAL_REL_TABLE_LIST:
return {
...state,
modify: {
...state.modify,
relAdd: {
...state.modify.relAdd,
manualRelInfo: {
...state.modify.relAdd.manualRelInfo,
tables: action.data,
},
},
},
};
case RESET_MANUAL_REL_TABLE_LIST:
return {
...state,
modify: {
...state.modify,
relAdd: {
...state.modify.relAdd,
manualRelInfo: { ...defaultState.modify.relAdd.manualRelInfo },
},
},
};
default:
return state;
}
@ -395,4 +465,7 @@ export {
fetchSchemaList,
ACCESS_KEY_ERROR,
UPDATE_DATA_HEADERS,
UPDATE_REMOTE_SCHEMA_MANUAL_REL,
fetchTableListBySchema,
RESET_MANUAL_REL_TABLE_LIST,
};

View File

@ -79,6 +79,10 @@ const defaultModifyState = {
rcol: '',
manualColumns: [],
isManualExpanded: false,
manualRelInfo: {
remoteSchema: '',
tables: [],
},
},
permissionsState: { ...defaultPermissionsState },
ongoingRequest: false,
@ -108,6 +112,7 @@ const defaultState = {
lastSuccess: null,
},
allSchemas: [],
listingSchemas: [],
untrackedSchemas: [],
information_schema: [],

View File

@ -116,7 +116,7 @@ hr
.relBlockInline {
display: inline-block;
width: 20%;
width: 16%;
}
.relBlockLeft {

View File

@ -2,6 +2,7 @@ import {
makeMigrationCall,
loadUntrackedRelations,
loadSchema,
RESET_MANUAL_REL_TABLE_LIST,
} from '../DataActions';
import gqlPattern, { gqlRelErrorNotif } from '../Common/GraphQLValidation';
import { showErrorNotification } from '../Notification';
@ -158,16 +159,23 @@ const relTableChange = tableName => (dispatch, getState) => {
// set table name state
dispatch({ type: REL_SET_RTABLE, rTable: tableName });
// fetch columns of the selected table
const tableSchema = getState().tables.allSchemas.find(
const tableSchema = getState().tables.modify.relAdd.manualRelInfo.tables.find(
t => t.table_name === tableName
);
const tableColumns = tableSchema.columns;
dispatch({ type: REL_SET_MANUAL_COLUMNS, data: tableColumns });
if (tableSchema) {
const tableColumns = tableSchema.columns;
dispatch({ type: REL_SET_MANUAL_COLUMNS, data: tableColumns });
} else {
console.error(`cannot find table: ${tableName}`);
dispatch({ type: REL_SET_MANUAL_COLUMNS, data: [] });
}
};
const addRelViewMigrate = tableName => (dispatch, getState) => {
const state = getState().tables.modify.relAdd;
const currentSchema = getState().tables.currentSchema;
const remoteSchema = getState().tables.modify.relAdd.manualRelInfo
.remoteSchema;
const isObjRel = state.isObjRel;
const name = state.name;
const lcol = state.lcol;
@ -185,7 +193,7 @@ const addRelViewMigrate = tableName => (dispatch, getState) => {
table: { name: tableName, schema: currentSchema },
using: {
manual_configuration: {
remote_table: { name: state.rTable, schema: currentSchema },
remote_table: { name: state.rTable, schema: remoteSchema },
column_mapping: columnMapping,
},
},
@ -209,7 +217,11 @@ const addRelViewMigrate = tableName => (dispatch, getState) => {
const successMsg = 'Relationship created';
const errorMsg = 'Creating relationship failed';
const customOnSuccess = () => {};
const customOnSuccess = () => {
/* Adds reset manual relationship state and closes the rel block */
dispatch({ type: RESET_MANUAL_REL_TABLE_LIST });
dispatch(relManualAddClicked());
};
const customOnError = () => {};
// perform validations and make call

View File

@ -0,0 +1,223 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import {
relNameChanged,
relTableChange,
REL_SET_LCOL,
REL_SET_RCOL,
relManualAddClicked,
relTypeChange,
addRelViewMigrate,
} from './Actions';
import {
fetchTableListBySchema,
UPDATE_REMOTE_SCHEMA_MANUAL_REL,
RESET_MANUAL_REL_TABLE_LIST,
} from '../DataActions';
class AddManualRelationship extends Component {
constructor() {
super();
this.onTableChange = this.onTableChange.bind(this);
this.onSchemaChange = this.onSchemaChange.bind(this);
this.onRelNameChange = this.onRelNameChange.bind(this);
this.onRelLColChange = this.onRelLColChange.bind(this);
this.onRelRColChange = this.onRelRColChange.bind(this);
this.onRelTypeChange = this.onRelTypeChange.bind(this);
this.onAddRelClicked = this.onAddRelClicked.bind(this);
this.onCloseClicked = this.onCloseClicked.bind(this);
}
componentDidMount() {
/* Initializing manual relationship config with current schema and tables */
this.props.dispatch({
type: UPDATE_REMOTE_SCHEMA_MANUAL_REL,
data: this.props.currentSchema,
});
this.props.dispatch(fetchTableListBySchema(this.props.currentSchema));
}
componentWillUnmount() {
this.props.dispatch({ type: RESET_MANUAL_REL_TABLE_LIST });
}
onTableChange(e) {
this.props.dispatch(relTableChange(e.target.value));
}
onSchemaChange(e) {
this.props.dispatch({
type: UPDATE_REMOTE_SCHEMA_MANUAL_REL,
data: e.target.value,
});
this.props.dispatch(fetchTableListBySchema(e.target.value));
}
onRelNameChange(e) {
this.props.dispatch(relNameChanged(e.target.value));
}
onRelLColChange(e) {
this.props.dispatch({ type: REL_SET_LCOL, lcol: e.target.value });
}
onRelRColChange(e) {
this.props.dispatch({ type: REL_SET_RCOL, rcol: e.target.value });
}
onRelTypeChange(e) {
if (e.target.value === 'object_rel') {
this.props.dispatch(relTypeChange('true'));
} else {
this.props.dispatch(relTypeChange('false'));
}
}
onAddRelClicked() {
this.props.dispatch(addRelViewMigrate(this.props.tableName));
}
onCloseClicked() {
this.props.dispatch(relManualAddClicked());
}
render() {
const styles = require('../TableModify/Modify.scss');
const {
tableName,
allSchemas,
schemaList,
manualColumns,
manualRelInfo,
titleInfo,
} = this.props;
const tableSchema = allSchemas.find(t => t.table_name === tableName);
return (
<div>
<div className={styles.subheading_text}> {titleInfo} </div>
<div className="form-group">
<div className={`${styles.relBlockInline} ${styles.relBlockLeft}`}>
Relationship Type
</div>
<div className={`${styles.relBlockInline} ${styles.relBlockRight}`}>
<select
className="form-control"
onChange={this.onRelTypeChange}
data-test="rel-type"
>
<option key="select_type" value="select_type">
Select relationship type
</option>
<option key="object" value="object_rel">
Object Relationship
</option>
<option key="array" value="array_rel">
Array Relationship
</option>
</select>
</div>
</div>
<div className="form-group">
<div className={`${styles.relBlockInline} ${styles.relBlockLeft}`}>
Relationship Name
</div>
<div className={`${styles.relBlockInline} ${styles.relBlockRight}`}>
<input
onChange={this.onRelNameChange}
className="form-control"
placeholder="Enter relationship name"
data-test="rel-name"
/>
</div>
</div>
<div className="form-group">
<div className={`${styles.relBlockInline} ${styles.relBlockLeft}`}>
Configuration
</div>
<select
className={`${styles.relBlockInline} form-control ${
styles.manual_rel_select
}`}
onChange={this.onRelLColChange}
data-test="current-col"
>
<option key="default_column">Current Column</option>
{tableSchema.columns.map((c, i) => (
<option key={c + i} value={c.column_name}>
{c.column_name}
</option>
))}
</select>
<span> :: </span>
<div className={styles.relBlockInline}>
<select
className={'form-control'}
onChange={this.onSchemaChange}
data-test="remote-schema"
value={manualRelInfo.remoteSchema}
>
<option key="default_table">Remote Schema</option>
{schemaList.map((s, i) => (
<option key={i} value={s.schema_name}>
{s.schema_name}
</option>
))}
</select>
</div>
<span> . </span>
<div className={styles.relBlockInline}>
<select
className={'form-control'}
onChange={this.onTableChange}
data-test="remote-table"
>
<option key="default_table">Remote Table</option>
{manualRelInfo.tables.map((s, i) => (
<option key={i} value={s.table_name}>
{s.table_name}
</option>
))}
</select>
</div>
<span> -> </span>
<div className={styles.relBlockInline}>
<select
className={'form-control'}
onChange={this.onRelRColChange}
data-test="remote-table-col"
>
<option key="default_table_column">Remote Table Column:</option>
{manualColumns.map((c, i) => (
<option key={c + i} value={c.column_name}>
{c.column_name}
</option>
))}
</select>
</div>
</div>
<button
className={styles.yellow_button}
onClick={this.onAddRelClicked}
data-test={this.props.dataTestVal}
>
Add
</button>
{this.props.showClose ? (
<button
className={styles.add_mar_left + ' btn btn-default btn-sm'}
onClick={this.onCloseClicked}
data-test="table-close-manual-relationship"
>
Close
</button>
) : null}
</div>
);
}
}
AddManualRelationship.propTypes = {
tableName: PropTypes.string.isRequired,
titleInfo: PropTypes.string.isRequired,
allSchemas: PropTypes.array.isRequired,
manualColumns: PropTypes.array.isRequired,
dispatch: PropTypes.func.isRequired,
manualRelInfo: PropTypes.object.isRequired,
schemaList: PropTypes.array.isRequired,
showClose: PropTypes.bool.isRequired,
dataTestVal: PropTypes.string.isRequired,
};
export default AddManualRelationship;

View File

@ -9,18 +9,15 @@ import {
relSelectionChanged,
relNameChanged,
resetRelationshipForm,
relTableChange,
REL_SET_LCOL,
REL_SET_RCOL,
relManualAddClicked,
relTypeChange,
addRelViewMigrate,
} from './Actions';
import { findAllFromRel } from '../utils';
import { showErrorNotification } from '../Notification';
import { setTable } from '../DataActions';
import gqlPattern, { gqlRelErrorNotif } from '../Common/GraphQLValidation';
import AddManualRelationship from './AddManualRelationship';
/* Gets the complete list of relationships and converts it to a list of object, which looks like so :
{
objRel: {objectRelationship},
@ -464,152 +461,11 @@ const AddRelationship = ({
);
};
const AddManualRelationship = ({
tableName,
allSchemas,
manualColumns,
dispatch,
}) => {
const styles = require('../TableModify/Modify.scss');
const tableSchema = allSchemas.find(t => t.table_name === tableName);
const onTableChange = e => {
dispatch(relTableChange(e.target.value));
};
const onRelNameChange = e => {
dispatch(relNameChanged(e.target.value));
};
const onRelLColChange = e => {
dispatch({ type: REL_SET_LCOL, lcol: e.target.value });
};
const onRelRColChange = e => {
dispatch({ type: REL_SET_RCOL, rcol: e.target.value });
};
const onRelTypeChange = e => {
if (e.target.value === 'object_rel') {
dispatch(relTypeChange('true'));
} else {
dispatch(relTypeChange('false'));
}
};
const onAddRelClicked = () => {
dispatch(addRelViewMigrate(tableName));
};
const onCloseClicked = () => {
dispatch(relManualAddClicked());
};
return (
<div>
<div className={styles.subheading_text}>
{' '}
Add a relationship manually{' '}
</div>
<div className="form-group">
<div className={`${styles.relBlockInline} ${styles.relBlockLeft}`}>
Relationship Type
</div>
<div className={`${styles.relBlockInline} ${styles.relBlockRight}`}>
<select
className="form-control"
onChange={onRelTypeChange}
data-test="rel-type"
>
<option key="select_type" value="select_type">
Select relationship type
</option>
<option key="object" value="object_rel">
Object Relationship
</option>
<option key="array" value="array_rel">
Array Relationship
</option>
</select>
</div>
</div>
<div className="form-group">
<div className={`${styles.relBlockInline} ${styles.relBlockLeft}`}>
Relationship Name
</div>
<div className={`${styles.relBlockInline} ${styles.relBlockRight}`}>
<input
onChange={onRelNameChange}
className="form-control"
placeholder="Enter relationship name"
data-test="rel-name"
/>
</div>
</div>
<div className="form-group">
<div className={`${styles.relBlockInline} ${styles.relBlockLeft}`}>
Configuration
</div>
<select
className={`${styles.relBlockInline} form-control`}
onChange={onRelLColChange}
data-test="current-col"
>
<option key="default_column">Current Column</option>
{tableSchema.columns.map((c, i) => (
<option key={c + i} value={c.column_name}>
{c.column_name}
</option>
))}
</select>
<span> :: </span>
<div className={styles.relBlockInline}>
<select
className="form-control"
onChange={onTableChange}
data-test="remote-table"
>
<option key="default_table">Remote Table</option>
{allSchemas.map((s, i) => (
<option key={i} value={s.table_name}>
{s.table_name}
</option>
))}
</select>
</div>
<span> -> </span>
<div className={styles.relBlockInline}>
<select
className="form-control"
onChange={onRelRColChange}
data-test="remote-table-col"
>
<option key="default_table_column">Remote Table Column:</option>
{manualColumns.map((c, i) => (
<option key={c + i} value={c.column_name}>
{c.column_name}
</option>
))}
</select>
</div>
</div>
<button
className={styles.yellow_button}
onClick={onAddRelClicked}
data-test="table-add-manual-relationship"
>
Add
</button>
<button
className={styles.add_mar_left + ' btn btn-default btn-sm'}
onClick={onCloseClicked}
data-test="table-close-manual-relationship"
>
Close
</button>
</div>
);
};
class Relationships extends Component {
componentDidMount() {
this.props.dispatch({ type: RESET });
this.props.dispatch(setTable(this.props.tableName));
}
render() {
const {
tableName,
@ -622,6 +478,7 @@ class Relationships extends Component {
relAdd,
migrationMode,
currentSchema,
schemaList,
} = this.props;
const styles = require('../TableModify/Modify.scss');
const tableStyles = require('../TableCommon/TableStyles.scss');
@ -750,7 +607,7 @@ class Relationships extends Component {
</div>
</div>
<div className={`${styles.padd_left_remove} container-fluid`}>
<div className={`${styles.padd_left_remove} col-xs-8`}>
<div className={`${styles.padd_left_remove} col-xs-10 col-md-10`}>
{relAdd.isManualExpanded ? (
<div className={styles.activeEdit}>
<AddManualRelationship
@ -761,7 +618,13 @@ class Relationships extends Component {
lcol={relAdd.lcol}
rcol={relAdd.rcol}
allSchemas={allSchemas}
schemaList={schemaList}
manualColumns={relAdd.manualColumns}
manualRelInfo={relAdd.manualRelInfo}
titleInfo={'Add a relationship manually'}
currentSchema={currentSchema}
showClose
dataTestVal={'table-add-manual-relationship'}
/>
</div>
) : (
@ -805,6 +668,7 @@ const mapStateToProps = (state, ownProps) => ({
allSchemas: state.tables.allSchemas,
migrationMode: state.main.migrationMode,
currentSchema: state.tables.currentSchema,
schemaList: state.tables.schemaList,
...state.tables.modify,
});

View File

@ -2,18 +2,11 @@ import React, { Component } from 'react';
import PropTypes from 'prop-types';
import ViewHeader from '../TableBrowseRows/ViewHeader';
import { RESET } from '../TableModify/ModifyActions';
import {
deleteRelMigrate,
addNewRelClicked,
relTableChange,
relNameChanged,
REL_SET_LCOL,
REL_SET_RCOL,
relTypeChange,
addRelViewMigrate,
} from './Actions';
import { deleteRelMigrate, addNewRelClicked } from './Actions';
import { findAllFromRel } from '../utils';
import { setTable } from '../DataActions';
import { setTable, UPDATE_REMOTE_SCHEMA_MANUAL_REL } from '../DataActions';
import AddRelationship from './AddManualRelationship';
/* Gets the complete list of relationships and converts it to a list of object, which looks like so :
{
@ -90,141 +83,15 @@ const relationshipView = (
);
};
const AddRelationship = ({
tableName,
allSchemas,
manualColumns,
dispatch,
}) => {
// eslint-disable-line no-unused-vars
const styles = require('../TableModify/Modify.scss');
const tableSchema = allSchemas.find(t => t.table_name === tableName);
const onTableChange = e => {
dispatch(relTableChange(e.target.value));
};
const onRelNameChange = e => {
dispatch(relNameChanged(e.target.value));
};
const onRelLColChange = e => {
dispatch({ type: REL_SET_LCOL, lcol: e.target.value });
};
const onRelRColChange = e => {
dispatch({ type: REL_SET_RCOL, rcol: e.target.value });
};
const onRelTypeChange = e => {
if (e.target.value === 'object_rel') {
dispatch(relTypeChange('true'));
} else {
dispatch(relTypeChange('false'));
}
};
const onAddRelClicked = () => {
dispatch(addRelViewMigrate(tableName));
};
return (
<div>
<div>
<div className={styles.subheading_text}> Add new relationship </div>
<div className="form-group">
<div className={`${styles.relBlockInline} ${styles.relBlockLeft}`}>
Relationship Type
</div>
<div className={`${styles.relBlockInline} ${styles.relBlockRight}`}>
<select
data-test="data-rel-type"
className="form-control"
onChange={onRelTypeChange}
data-test="rel-type"
>
<option key="select_type" value="select_type">
Select relationship type
</option>
<option key="object" value="object_rel">
Object Relationship
</option>
<option key="array" value="array_rel">
Array Relationship
</option>
</select>
</div>
</div>
<div className="form-group">
<div className={`${styles.relBlockInline} ${styles.relBlockLeft}`}>
Relationship Name
</div>
<div className={`${styles.relBlockInline} ${styles.relBlockRight}`}>
<input
onChange={onRelNameChange}
className="form-control"
placeholder="Enter relationship name"
data-test="rel-name"
/>
</div>
</div>
<div className="form-group">
<div className={`${styles.relBlockInline} ${styles.relBlockLeft}`}>
Configuration
</div>
<select
className={`${styles.relBlockInline} form-control`}
onChange={onRelLColChange}
data-test="current-col"
>
<option key="default_column">Current Column</option>
{tableSchema.columns.map((c, i) => (
<option key={c + i} value={c.column_name}>
{c.column_name}
</option>
))}
</select>
<span> :: </span>
<div className={styles.relBlockInline}>
<select
className="form-control"
onChange={onTableChange}
data-test="remote-table"
>
<option key="default_table">Remote Table</option>
{allSchemas.map((s, i) => (
<option key={i} value={s.table_name}>
{s.table_name}
</option>
))}
</select>
</div>
<span> -> </span>
<div className={styles.relBlockInline}>
<select
className="form-control"
onChange={onRelRColChange}
data-test="remote-table-col"
>
<option key="default_table_column">Remote Table Column:</option>
{manualColumns.map((c, i) => (
<option key={c + i} value={c.column_name}>
{c.column_name}
</option>
))}
</select>
</div>
</div>
<button
className={styles.yellow_button}
onClick={onAddRelClicked}
data-test="view-add-relationship"
>
Add
</button>
</div>
</div>
);
};
class RelationshipsView extends Component {
componentDidMount() {
this.props.dispatch({ type: RESET });
this.props.dispatch(setTable(this.props.tableName));
// Sourcing the current schema into manual relationship
this.props.dispatch({
type: UPDATE_REMOTE_SCHEMA_MANUAL_REL,
data: this.props.currentSchema,
});
}
render() {
@ -239,6 +106,7 @@ class RelationshipsView extends Component {
relAdd,
currentSchema,
migrationMode,
schemaList,
} = this.props;
const styles = require('../TableModify/Modify.scss');
const tableStyles = require('../TableCommon/TableStyles.scss');
@ -338,7 +206,7 @@ class RelationshipsView extends Component {
/>
<br />
<div className={`${styles.padd_left_remove} container-fluid`}>
<div className={`${styles.padd_left_remove} col-xs-8`}>
<div className={`${styles.padd_left_remove} col-xs-10 col-md-10`}>
<h4 className={styles.subheading_text}>Relationships</h4>
{addedRelationshipsView}
<br />
@ -352,7 +220,13 @@ class RelationshipsView extends Component {
lcol={relAdd.lcol}
rcol={relAdd.rcol}
allSchemas={allSchemas}
schemaList={schemaList}
manualRelInfo={relAdd.manualRelInfo}
manualColumns={relAdd.manualColumns}
titleInfo={'Add new relationship'}
currentSchema={currentSchema}
showClose={false}
dataTestVal={'view-add-relationship'}
/>
</div>
) : (
@ -394,6 +268,7 @@ const mapStateToProps = (state, ownProps) => ({
allSchemas: state.tables.allSchemas,
currentSchema: state.tables.currentSchema,
migrationMode: state.main.migrationMode,
schemaList: state.tables.schemaList,
...state.tables.modify,
});