close #57 fix messaging, expand track all relations (#88)

This commit is contained in:
Praveen Durairaj 2018-07-11 10:28:52 +05:30 committed by Shahidh K Muhammed
parent 83ab85fb04
commit 113b6a9bb5
16 changed files with 283 additions and 58 deletions

View File

@ -39,7 +39,6 @@
} }
.cursorNotAllowed { .cursorNotAllowed {
cursor: not-allowed; cursor: not-allowed;
pointer-events: none;
} }
.apiExplorerWrapper .apiExplorerWrapper
{ {

View File

@ -27,7 +27,7 @@ dataApisContent.push({
details: { details: {
title: 'GraphQL API', title: 'GraphQL API',
description: description:
'GraphQL API for CRUD operations on tables/views in your database', 'GraphQL API for CRUD operations on tables & views in your database',
category: 'data', category: 'data',
}, },
request: { request: {

View File

@ -380,6 +380,10 @@ input {
{ {
padding-left: 5px; padding-left: 5px;
} }
.padd_small_right
{
padding-right: 5px;
}
.border_bottom .border_bottom
{ {
border-bottom: 1px solid #e7e7e7; border-bottom: 1px solid #e7e7e7;
@ -495,6 +499,10 @@ code
{ {
-webkit-padding-start: 15px; -webkit-padding-start: 15px;
} }
.add_pad_bottom
{
padding-bottom: 20px;
}
.add_padd_bottom .add_padd_bottom
{ {
padding-bottom: 25px; padding-bottom: 25px;
@ -985,6 +993,7 @@ code
.graphQLHeight { .graphQLHeight {
height: 100vh; height: 100vh;
margin-bottom: 50px;
} }
/* container height subtracting top header and bottom scroll bar */ /* container height subtracting top header and bottom scroll bar */

View File

@ -31,11 +31,18 @@ const Main = ({ children, location, migrationModeProgress, currentSchema }) => {
let accessKeyHtml = null; let accessKeyHtml = null;
if (globals.accessKey === '' || globals.accessKey === null) { if (globals.accessKey === '' || globals.accessKey === null) {
accessKeyHtml = ( accessKeyHtml = (
<a href="https://docs.hasura.io/1.0/graphql/manual/deployment/production.html"> <OverlayTrigger placement="left" overlay={tooltip.secureEndpoint}>
<button className={'btn btn-danger ' + styles.add_mar_right}> <a href="https://docs.hasura.io/1.0/graphql/manual/deployment/production.html">
Secure your endpoint <button className={'btn btn-danger ' + styles.add_mar_right}>
</button> <i
</a> className={
styles.padd_small_right + ' fa fa-exclamation-triangle'
}
/>
Secure your endpoint
</button>
</a>
</OverlayTrigger>
); );
} }
@ -75,7 +82,7 @@ const Main = ({ children, location, migrationModeProgress, currentSchema }) => {
aria-hidden="true" aria-hidden="true"
/> />
</div> </div>
<p>API Explorer</p> <p>GraphiQL</p>
</Link> </Link>
</li> </li>
</OverlayTrigger> </OverlayTrigger>

View File

@ -6,5 +6,11 @@ export const data = (
); );
export const apiexplorer = ( export const apiexplorer = (
<Tooltip id="tooltip-api-explorer">Test the Hasura APIs</Tooltip> <Tooltip id="tooltip-api-explorer">Test the GraphQL APIs</Tooltip>
);
export const secureEndpoint = (
<Tooltip id="tooltip-secure-endpoint">
This graphql endpoint is public and you should add an access key
</Tooltip>
); );

View File

@ -456,16 +456,7 @@ class AddTable extends Component {
<div <div
className={`${styles.addCol} col-xs-12 ${styles.padd_left_remove}`} className={`${styles.addCol} col-xs-12 ${styles.padd_left_remove}`}
> >
<h4 className={styles.subheading_text}> <h4 className={styles.subheading_text}>Table name &nbsp; &nbsp;</h4>
Table name: &nbsp; &nbsp;
<OverlayTrigger
placement="right"
overlay={tooltip.databaseNamingScheme}
>
<i className="fa fa-question-circle" aria-hidden="true" />
</OverlayTrigger>{' '}
&nbsp; &nbsp;
</h4>
<input <input
type="text" type="text"
data-test="tableName" data-test="tableName"
@ -480,7 +471,7 @@ class AddTable extends Component {
{cols} {cols}
<hr /> <hr />
<h4 className={styles.subheading_text}> <h4 className={styles.subheading_text}>
Primary Key: &nbsp; &nbsp; Primary Key &nbsp; &nbsp;
<OverlayTrigger <OverlayTrigger
placement="right" placement="right"
overlay={tooltip.primaryKeyDescription} overlay={tooltip.primaryKeyDescription}

View File

@ -42,6 +42,16 @@ const fetchSchemaList = () => (dispatch, getState) => {
schema: 'information_schema', schema: 'information_schema',
}, },
columns: ['schema_name'], columns: ['schema_name'],
where: {
schema_name: {
$nin: [
'information_schema',
'pg_catalog',
'hdb_catalog',
'hdb_views',
],
},
},
}, },
}), }),
}; };

View File

@ -28,7 +28,7 @@ const DataHeader = ({
/* /*
const handleSchemaChange = e => { const handleSchemaChange = e => {
const updatedSchema = e.target.value; const updatedSchema = e.target.value;
dispatch(push(globals.urlPrefix + '/data/schema/' + updatedSchema)); dispatch(push('/data/schema/' + updatedSchema));
Promise.all([ Promise.all([
dispatch({ type: UPDATE_CURRENT_SCHEMA, currentSchema: updatedSchema }), dispatch({ type: UPDATE_CURRENT_SCHEMA, currentSchema: updatedSchema }),
dispatch(loadSchema()), dispatch(loadSchema()),

View File

@ -129,7 +129,7 @@ const PageContainer = ({
styles.padd_left_remove styles.padd_left_remove
} }
> >
Tables Tables ({schema.length})
</div> </div>
{migrationMode ? ( {migrationMode ? (
<div <div

View File

@ -20,6 +20,12 @@
line-height: 26px; line-height: 26px;
} }
.changeSchema {
height: 25px;
margin-left: 10px;
width: auto;
}
.sidebar { .sidebar {
height: calc(100vh - 26px); height: calc(100vh - 26px);
overflow: auto; overflow: auto;

View File

@ -4,7 +4,12 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import { autoTrackRelations } from '../TableRelationships/Actions'; import {
autoTrackRelations,
autoAddRelName,
} from '../TableRelationships/Actions';
import { getRelationshipLine } from '../TableRelationships/Relationships';
import suggestedRelationshipsRaw from '../TableRelationships/autoRelations';
class AutoAddRelations extends Component { class AutoAddRelations extends Component {
componentWillMount() { componentWillMount() {
@ -14,20 +19,110 @@ class AutoAddRelations extends Component {
this.props.dispatch(autoTrackRelations()); this.props.dispatch(autoTrackRelations());
}; };
render() { render() {
// const styles = require('../PageContainer/PageContainer.scss'); const { schema, untrackedRelations, dispatch } = this.props;
const styles = require('../PageContainer/PageContainer.scss');
const handleAutoAddIndivRel = obj => {
dispatch(autoAddRelName(obj));
};
if (this.props.untrackedRelations.length === 0) { if (untrackedRelations.length === 0) {
return null; return (
<div
className={styles.display_inline + ' ' + styles.padd_bottom}
key="no-untracked-rel"
>
There are no untracked relations
</div>
);
} }
const untrackedIndivHtml = [];
schema.map(table => {
const currentTable = table.table_name;
const currentTableRel = suggestedRelationshipsRaw(currentTable, schema);
currentTableRel.objectRel.map(obj => {
untrackedIndivHtml.push(
<div
className={styles.padd_top_medium}
key={'untrackedIndiv' + table.table_name}
>
<button
className={`${styles.display_inline} btn btn-xs btn-default`}
onClick={e => {
e.preventDefault();
handleAutoAddIndivRel(obj);
}}
>
Add
</button>
<div className={styles.display_inline + ' ' + styles.add_pad_left}>
<b>{obj.tableName}</b> -{' '}
{getRelationshipLine(
obj.isObjRel,
obj.lcol,
obj.rcol,
obj.rTable
)}
</div>
</div>
);
});
currentTableRel.arrayRel.map(obj => {
untrackedIndivHtml.push(
<div
className={styles.padd_top_medium}
key={'untrackedIndiv' + table.table_name}
>
<button
className={`${styles.display_inline} btn btn-xs btn-default`}
onClick={e => {
e.preventDefault();
handleAutoAddIndivRel(obj);
}}
>
Add
</button>
<div className={styles.display_inline + ' ' + styles.add_pad_left}>
<b>{obj.tableName}</b> -{' '}
{getRelationshipLine(
obj.isObjRel,
obj.lcol,
obj.rcol,
obj.rTable
)}
</div>
</div>
);
});
});
return ( return (
<div> <div>
{untrackedRelations.length === 0 ? (
<div
className={styles.display_inline + ' ' + styles.padd_bottom}
key="no-untracked-rel"
>
There are no untracked relations
</div>
) : (
<div
className={styles.display_inline + ' ' + styles.padd_bottom}
key="untracked-rel"
>
There are {untrackedRelations.length} untracked relations
</div>
)}
<button <button
onClick={this.trackAllRelations} onClick={this.trackAllRelations}
className={'btn btn-xs btn-default'} className={
styles.display_inline +
' btn btn-xs btn-default ' +
styles.add_mar_left
}
data-test="track-all-relationships" data-test="track-all-relationships"
> >
Track Available Relations Track All Relations
</button> </button>
<div className={styles.padd_top_small}>{untrackedIndivHtml}</div>
</div> </div>
); );
} }
@ -35,6 +130,7 @@ class AutoAddRelations extends Component {
AutoAddRelations.propTypes = { AutoAddRelations.propTypes = {
untrackedRelations: PropTypes.array.isRequired, untrackedRelations: PropTypes.array.isRequired,
schema: PropTypes.array.isRequired,
dispatch: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired,
}; };

View File

@ -19,6 +19,7 @@ import {
loadUntrackedSchema, loadUntrackedSchema,
fetchSchemaList, fetchSchemaList,
LOAD_UNTRACKED_RELATIONS, LOAD_UNTRACKED_RELATIONS,
UPDATE_CURRENT_SCHEMA,
} from '../DataActions'; } from '../DataActions';
import { getAllUnTrackedRelations } from '../TableRelationships/Actions'; import { getAllUnTrackedRelations } from '../TableRelationships/Actions';
import AutoAddRelationsConnector from './AutoAddRelations'; import AutoAddRelationsConnector from './AutoAddRelations';
@ -57,12 +58,24 @@ class Schema extends Component {
render() { render() {
const { const {
schema, schema,
schemaList,
untracked, untracked,
migrationMode, migrationMode,
untrackedRelations, untrackedRelations,
currentSchema, currentSchema,
dispatch, dispatch,
} = this.props; } = this.props;
const handleSchemaChange = e => {
const updatedSchema = e.target.value;
dispatch(push('/data/schema/' + updatedSchema));
Promise.all([
dispatch({ type: UPDATE_CURRENT_SCHEMA, currentSchema: updatedSchema }),
dispatch(loadSchema()),
dispatch(loadUntrackedSchema()),
]);
};
const styles = require('../PageContainer/PageContainer.scss'); const styles = require('../PageContainer/PageContainer.scss');
let relationships = 0; let relationships = 0;
@ -89,7 +102,6 @@ class Schema extends Component {
const untrackedHtml = []; const untrackedHtml = [];
for (let i = 0; i < untrackedTables.length; i++) { for (let i = 0; i < untrackedTables.length; i++) {
// if (untrackedTables[i].table_name !== 'schema_migrations') {
untrackedHtml.push( untrackedHtml.push(
<div className={styles.padd_bottom} key={`${i}untracked`}> <div className={styles.padd_bottom} key={`${i}untracked`}>
<div className={`${styles.display_inline} ${styles.padd_right}`}> <div className={`${styles.display_inline} ${styles.padd_right}`}>
@ -110,7 +122,6 @@ class Schema extends Component {
</div> </div>
</div> </div>
); );
// }
} }
if (!untrackedHtml.length) { if (!untrackedHtml.length) {
untrackedHtml.push( untrackedHtml.push(
@ -147,18 +158,35 @@ class Schema extends Component {
) : null} ) : null}
</div> </div>
<hr /> <hr />
<div className={styles.padd_bottom}> <div>
There are <b>{schema.length}</b> tables tracked in the{' '} <div className={styles.display_inline}>Current postgres schema</div>
{currentSchema} schema, with <b>{relationships}</b> relationships. <div className={styles.display_inline}>
<select
onChange={handleSchemaChange}
className={styles.changeSchema + ' form-control'}
>
{schemaList.map(s => {
if (s.schema_name === currentSchema) {
return (
<option key={s.schema_name} selected="selected">
{s.schema_name}
</option>
);
}
return <option key={s.schema_name}>{s.schema_name}</option>;
})}
</select>
</div>
</div> </div>
<div className={styles.padd_top}> <hr />
<div className={styles.add_pad_bottom}>
<div> <div>
<h4 <h4
className={`${styles.subheading_text} ${ className={`${styles.subheading_text} ${
styles.heading_tooltip styles.heading_tooltip
}`} }`}
> >
Untracked Tables/Views Untracked tables or views
</h4> </h4>
<OverlayTrigger placement="right" overlay={untrackedTip}> <OverlayTrigger placement="right" overlay={untrackedTip}>
<i className="fa fa-info-circle" aria-hidden="true" /> <i className="fa fa-info-circle" aria-hidden="true" />
@ -181,28 +209,27 @@ class Schema extends Component {
{untrackedHtml} {untrackedHtml}
</div> </div>
</div> </div>
<div className={styles.padd_top}> <hr />
<div className={styles.padd_top}> <div>
<div>
<h4 <h4
className={`${styles.subheading_text} ${ className={`${styles.subheading_text} ${
styles.heading_tooltip styles.heading_tooltip
}`} }`}
> >
Untracked Relations Untracked foreign-key relations
</h4> </h4>
<OverlayTrigger placement="right" overlay={untrackedRelTip}> <OverlayTrigger placement="right" overlay={untrackedRelTip}>
<i className="fa fa-info-circle" aria-hidden="true" /> <i className="fa fa-info-circle" aria-hidden="true" />
</OverlayTrigger> </OverlayTrigger>
<div className={`${styles.padd_left_remove} col-xs-12`}> <div className={`${styles.padd_left_remove} col-xs-12`}>
{untrackedRelations.length === 0 ? ( <div>
<div key="no-untracked-rel"> <AutoAddRelationsConnector
There are no untracked relations untrackedRelations={untrackedRelations}
</div> schema={schema}
) : null} dispatch={dispatch}
<AutoAddRelationsConnector />
untrackedRelations={untrackedRelations} </div>
dispatch={dispatch}
/>
</div> </div>
</div> </div>
</div> </div>
@ -223,6 +250,7 @@ Schema.propTypes = {
const mapStateToProps = state => ({ const mapStateToProps = state => ({
schema: state.tables.allSchemas, schema: state.tables.allSchemas,
schemaList: state.tables.schemaList,
untracked: state.tables.untrackedSchemas, untracked: state.tables.untrackedSchemas,
migrationMode: state.main.migrationMode, migrationMode: state.main.migrationMode,
untrackedRelations: state.tables.untrackedRelations, untrackedRelations: state.tables.untrackedRelations,

View File

@ -10,15 +10,13 @@ export const dataAPI = (
export const untrackedTip = ( export const untrackedTip = (
<Tooltip id="tooltip-data-service"> <Tooltip id="tooltip-data-service">
These are the tables/views in the schema which are not tracked by Data Tables or views that are not exposed over GraphQL
Microservice
</Tooltip> </Tooltip>
); );
export const untrackedRelTip = ( export const untrackedRelTip = (
<Tooltip id="tooltip-data-rel-service"> <Tooltip id="tooltip-data-rel-service">
These are the relations in the schema which are not tracked by Data Foreign keys between tracked tables that are not relationships
Microservice
</Tooltip> </Tooltip>
); );

View File

@ -144,6 +144,7 @@ const modifyReducer = (tableName, schemas, modifyStateOrig, action) => {
lcol: '', lcol: '',
rTable: null, rTable: null,
rcol: '', rcol: '',
manualColumns: [],
}, },
}; };
case REL_SET_TYPE: case REL_SET_TYPE:

View File

@ -1,4 +1,8 @@
import { makeMigrationCall, loadUntrackedRelations } from '../DataActions'; import {
makeMigrationCall,
loadUntrackedRelations,
loadSchema,
} from '../DataActions';
import gqlPattern, { gqlRelErrorNotif } from '../Common/GraphQLValidation'; import gqlPattern, { gqlRelErrorNotif } from '../Common/GraphQLValidation';
import { showErrorNotification } from '../Notification'; import { showErrorNotification } from '../Notification';
import suggestedRelationshipsRaw from './autoRelations'; import suggestedRelationshipsRaw from './autoRelations';
@ -410,6 +414,78 @@ const autoTrackRelations = () => (dispatch, getState) => {
); );
}; };
const autoAddRelName = obj => (dispatch, getState) => {
const currentSchema = getState().tables.currentSchema;
const isObjRel = obj.isObjRel;
const relName = formRelName(obj);
const relChangesUp = [
{
type: isObjRel
? 'create_object_relationship'
: 'create_array_relationship',
args: {
name: relName,
table: { name: obj.tableName, schema: currentSchema },
using: isObjRel
? { foreign_key_constraint_on: obj.lcol }
: {
foreign_key_constraint_on: {
table: { name: obj.rTable, schema: currentSchema },
column: obj.rcol,
},
},
},
},
];
const relChangesDown = [
{
type: isObjRel
? 'create_object_relationship'
: 'create_array_relationship',
args: {
name: relName,
table: { name: obj.tableName, schema: currentSchema },
using: isObjRel
? { foreign_key_constraint_on: obj.lcol }
: {
foreign_key_constraint_on: {
table: { name: obj.rTable, schema: currentSchema },
column: obj.rcol,
},
},
},
},
];
// Apply migrations
const migrationName = `add_relationship_${relName}_table_${currentSchema}_${
obj.tableName
}`;
const requestMsg = 'Adding Relationship...';
const successMsg = 'Relationship created';
const errorMsg = 'Creating relationship failed';
const customOnSuccess = () => {
Promise.all([dispatch(loadSchema()), dispatch(loadUntrackedRelations())]);
};
const customOnError = () => {};
makeMigrationCall(
dispatch,
getState,
relChangesUp,
relChangesDown,
migrationName,
customOnSuccess,
customOnError,
requestMsg,
successMsg,
errorMsg
);
};
export { export {
deleteRelMigrate, deleteRelMigrate,
addNewRelClicked, addNewRelClicked,
@ -424,6 +500,7 @@ export {
resetRelationshipForm, resetRelationshipForm,
relManualAddClicked, relManualAddClicked,
autoTrackRelations, autoTrackRelations,
autoAddRelName,
formRelName, formRelName,
getAllUnTrackedRelations, getAllUnTrackedRelations,
}; };

View File

@ -46,16 +46,13 @@ const getObjArrayRelationshipList = relationships => {
/* This function sets the styling to the way the relationship looks, for eg: id -> user::user_id */ /* This function sets the styling to the way the relationship looks, for eg: id -> user::user_id */
const getRelationshipLine = (isObjRel, lcol, rcol, rTable) => { const getRelationshipLine = (isObjRel, lcol, rcol, rTable) => {
const finalRTable = rTable.name ? rTable.name : rTable; const finalRTable = rTable.name ? rTable.name : rTable;
const getGrayText = value => <i>{value}</i>;
return isObjRel ? ( return isObjRel ? (
<span> <span>
&nbsp;{getGrayText(lcol)}&nbsp;&nbsp;&rarr;&nbsp;&nbsp;{rTable} :: {rcol} &nbsp;{lcol}&nbsp;&nbsp;&rarr;&nbsp;&nbsp;{rTable} :: {rcol}
</span> </span>
) : ( ) : (
<span> <span>
&nbsp;{finalRTable} :: {rcol}&nbsp;&nbsp;&rarr;&nbsp;&nbsp;{getGrayText( &nbsp;{finalRTable} :: {rcol}&nbsp;&nbsp;&rarr;&nbsp;&nbsp;{lcol}
lcol
)}
</span> </span>
); );
}; };
@ -777,4 +774,4 @@ const relationshipsConnector = connect =>
export default relationshipsConnector; export default relationshipsConnector;
export { suggestedRelationships }; export { suggestedRelationships, getRelationshipLine };