mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-15 01:12:56 +03:00
parent
83ab85fb04
commit
113b6a9bb5
@ -39,7 +39,6 @@
|
||||
}
|
||||
.cursorNotAllowed {
|
||||
cursor: not-allowed;
|
||||
pointer-events: none;
|
||||
}
|
||||
.apiExplorerWrapper
|
||||
{
|
||||
|
@ -27,7 +27,7 @@ dataApisContent.push({
|
||||
details: {
|
||||
title: 'GraphQL API',
|
||||
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',
|
||||
},
|
||||
request: {
|
||||
|
@ -380,6 +380,10 @@ input {
|
||||
{
|
||||
padding-left: 5px;
|
||||
}
|
||||
.padd_small_right
|
||||
{
|
||||
padding-right: 5px;
|
||||
}
|
||||
.border_bottom
|
||||
{
|
||||
border-bottom: 1px solid #e7e7e7;
|
||||
@ -495,6 +499,10 @@ code
|
||||
{
|
||||
-webkit-padding-start: 15px;
|
||||
}
|
||||
.add_pad_bottom
|
||||
{
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
.add_padd_bottom
|
||||
{
|
||||
padding-bottom: 25px;
|
||||
@ -985,6 +993,7 @@ code
|
||||
|
||||
.graphQLHeight {
|
||||
height: 100vh;
|
||||
margin-bottom: 50px;
|
||||
}
|
||||
|
||||
/* container height subtracting top header and bottom scroll bar */
|
||||
|
@ -31,11 +31,18 @@ const Main = ({ children, location, migrationModeProgress, currentSchema }) => {
|
||||
let accessKeyHtml = null;
|
||||
if (globals.accessKey === '' || globals.accessKey === null) {
|
||||
accessKeyHtml = (
|
||||
<a href="https://docs.hasura.io/1.0/graphql/manual/deployment/production.html">
|
||||
<button className={'btn btn-danger ' + styles.add_mar_right}>
|
||||
Secure your endpoint
|
||||
</button>
|
||||
</a>
|
||||
<OverlayTrigger placement="left" overlay={tooltip.secureEndpoint}>
|
||||
<a href="https://docs.hasura.io/1.0/graphql/manual/deployment/production.html">
|
||||
<button className={'btn btn-danger ' + styles.add_mar_right}>
|
||||
<i
|
||||
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"
|
||||
/>
|
||||
</div>
|
||||
<p>API Explorer</p>
|
||||
<p>GraphiQL</p>
|
||||
</Link>
|
||||
</li>
|
||||
</OverlayTrigger>
|
||||
|
@ -6,5 +6,11 @@ export const data = (
|
||||
);
|
||||
|
||||
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>
|
||||
);
|
||||
|
@ -456,16 +456,7 @@ class AddTable extends Component {
|
||||
<div
|
||||
className={`${styles.addCol} col-xs-12 ${styles.padd_left_remove}`}
|
||||
>
|
||||
<h4 className={styles.subheading_text}>
|
||||
Table name:
|
||||
<OverlayTrigger
|
||||
placement="right"
|
||||
overlay={tooltip.databaseNamingScheme}
|
||||
>
|
||||
<i className="fa fa-question-circle" aria-hidden="true" />
|
||||
</OverlayTrigger>{' '}
|
||||
|
||||
</h4>
|
||||
<h4 className={styles.subheading_text}>Table name </h4>
|
||||
<input
|
||||
type="text"
|
||||
data-test="tableName"
|
||||
@ -480,7 +471,7 @@ class AddTable extends Component {
|
||||
{cols}
|
||||
<hr />
|
||||
<h4 className={styles.subheading_text}>
|
||||
Primary Key:
|
||||
Primary Key
|
||||
<OverlayTrigger
|
||||
placement="right"
|
||||
overlay={tooltip.primaryKeyDescription}
|
||||
|
@ -42,6 +42,16 @@ const fetchSchemaList = () => (dispatch, getState) => {
|
||||
schema: 'information_schema',
|
||||
},
|
||||
columns: ['schema_name'],
|
||||
where: {
|
||||
schema_name: {
|
||||
$nin: [
|
||||
'information_schema',
|
||||
'pg_catalog',
|
||||
'hdb_catalog',
|
||||
'hdb_views',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
@ -28,7 +28,7 @@ const DataHeader = ({
|
||||
/*
|
||||
const handleSchemaChange = e => {
|
||||
const updatedSchema = e.target.value;
|
||||
dispatch(push(globals.urlPrefix + '/data/schema/' + updatedSchema));
|
||||
dispatch(push('/data/schema/' + updatedSchema));
|
||||
Promise.all([
|
||||
dispatch({ type: UPDATE_CURRENT_SCHEMA, currentSchema: updatedSchema }),
|
||||
dispatch(loadSchema()),
|
||||
|
@ -129,7 +129,7 @@ const PageContainer = ({
|
||||
styles.padd_left_remove
|
||||
}
|
||||
>
|
||||
Tables
|
||||
Tables ({schema.length})
|
||||
</div>
|
||||
{migrationMode ? (
|
||||
<div
|
||||
|
@ -20,6 +20,12 @@
|
||||
line-height: 26px;
|
||||
}
|
||||
|
||||
.changeSchema {
|
||||
height: 25px;
|
||||
margin-left: 10px;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
height: calc(100vh - 26px);
|
||||
overflow: auto;
|
||||
|
@ -4,7 +4,12 @@
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
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 {
|
||||
componentWillMount() {
|
||||
@ -14,20 +19,110 @@ class AutoAddRelations extends Component {
|
||||
this.props.dispatch(autoTrackRelations());
|
||||
};
|
||||
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) {
|
||||
return null;
|
||||
if (untrackedRelations.length === 0) {
|
||||
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 (
|
||||
<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
|
||||
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"
|
||||
>
|
||||
Track Available Relations
|
||||
Track All Relations
|
||||
</button>
|
||||
<div className={styles.padd_top_small}>{untrackedIndivHtml}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -35,6 +130,7 @@ class AutoAddRelations extends Component {
|
||||
|
||||
AutoAddRelations.propTypes = {
|
||||
untrackedRelations: PropTypes.array.isRequired,
|
||||
schema: PropTypes.array.isRequired,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
|
@ -19,6 +19,7 @@ import {
|
||||
loadUntrackedSchema,
|
||||
fetchSchemaList,
|
||||
LOAD_UNTRACKED_RELATIONS,
|
||||
UPDATE_CURRENT_SCHEMA,
|
||||
} from '../DataActions';
|
||||
import { getAllUnTrackedRelations } from '../TableRelationships/Actions';
|
||||
import AutoAddRelationsConnector from './AutoAddRelations';
|
||||
@ -57,12 +58,24 @@ class Schema extends Component {
|
||||
render() {
|
||||
const {
|
||||
schema,
|
||||
schemaList,
|
||||
untracked,
|
||||
migrationMode,
|
||||
untrackedRelations,
|
||||
currentSchema,
|
||||
dispatch,
|
||||
} = 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');
|
||||
|
||||
let relationships = 0;
|
||||
@ -89,7 +102,6 @@ class Schema extends Component {
|
||||
|
||||
const untrackedHtml = [];
|
||||
for (let i = 0; i < untrackedTables.length; i++) {
|
||||
// if (untrackedTables[i].table_name !== 'schema_migrations') {
|
||||
untrackedHtml.push(
|
||||
<div className={styles.padd_bottom} key={`${i}untracked`}>
|
||||
<div className={`${styles.display_inline} ${styles.padd_right}`}>
|
||||
@ -110,7 +122,6 @@ class Schema extends Component {
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
// }
|
||||
}
|
||||
if (!untrackedHtml.length) {
|
||||
untrackedHtml.push(
|
||||
@ -147,18 +158,35 @@ class Schema extends Component {
|
||||
) : null}
|
||||
</div>
|
||||
<hr />
|
||||
<div className={styles.padd_bottom}>
|
||||
There are <b>{schema.length}</b> tables tracked in the{' '}
|
||||
{currentSchema} schema, with <b>{relationships}</b> relationships.
|
||||
<div>
|
||||
<div className={styles.display_inline}>Current postgres schema</div>
|
||||
<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 className={styles.padd_top}>
|
||||
<hr />
|
||||
<div className={styles.add_pad_bottom}>
|
||||
<div>
|
||||
<h4
|
||||
className={`${styles.subheading_text} ${
|
||||
styles.heading_tooltip
|
||||
}`}
|
||||
>
|
||||
Untracked Tables/Views
|
||||
Untracked tables or views
|
||||
</h4>
|
||||
<OverlayTrigger placement="right" overlay={untrackedTip}>
|
||||
<i className="fa fa-info-circle" aria-hidden="true" />
|
||||
@ -181,28 +209,27 @@ class Schema extends Component {
|
||||
{untrackedHtml}
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.padd_top}>
|
||||
<div className={styles.padd_top}>
|
||||
<hr />
|
||||
<div>
|
||||
<div>
|
||||
<h4
|
||||
className={`${styles.subheading_text} ${
|
||||
styles.heading_tooltip
|
||||
}`}
|
||||
>
|
||||
Untracked Relations
|
||||
Untracked foreign-key relations
|
||||
</h4>
|
||||
<OverlayTrigger placement="right" overlay={untrackedRelTip}>
|
||||
<i className="fa fa-info-circle" aria-hidden="true" />
|
||||
</OverlayTrigger>
|
||||
<div className={`${styles.padd_left_remove} col-xs-12`}>
|
||||
{untrackedRelations.length === 0 ? (
|
||||
<div key="no-untracked-rel">
|
||||
There are no untracked relations
|
||||
</div>
|
||||
) : null}
|
||||
<AutoAddRelationsConnector
|
||||
untrackedRelations={untrackedRelations}
|
||||
dispatch={dispatch}
|
||||
/>
|
||||
<div>
|
||||
<AutoAddRelationsConnector
|
||||
untrackedRelations={untrackedRelations}
|
||||
schema={schema}
|
||||
dispatch={dispatch}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -223,6 +250,7 @@ Schema.propTypes = {
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
schema: state.tables.allSchemas,
|
||||
schemaList: state.tables.schemaList,
|
||||
untracked: state.tables.untrackedSchemas,
|
||||
migrationMode: state.main.migrationMode,
|
||||
untrackedRelations: state.tables.untrackedRelations,
|
||||
|
@ -10,15 +10,13 @@ export const dataAPI = (
|
||||
|
||||
export const untrackedTip = (
|
||||
<Tooltip id="tooltip-data-service">
|
||||
These are the tables/views in the schema which are not tracked by Data
|
||||
Microservice
|
||||
Tables or views that are not exposed over GraphQL
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
export const untrackedRelTip = (
|
||||
<Tooltip id="tooltip-data-rel-service">
|
||||
These are the relations in the schema which are not tracked by Data
|
||||
Microservice
|
||||
Foreign keys between tracked tables that are not relationships
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
|
@ -144,6 +144,7 @@ const modifyReducer = (tableName, schemas, modifyStateOrig, action) => {
|
||||
lcol: '',
|
||||
rTable: null,
|
||||
rcol: '',
|
||||
manualColumns: [],
|
||||
},
|
||||
};
|
||||
case REL_SET_TYPE:
|
||||
|
@ -1,4 +1,8 @@
|
||||
import { makeMigrationCall, loadUntrackedRelations } from '../DataActions';
|
||||
import {
|
||||
makeMigrationCall,
|
||||
loadUntrackedRelations,
|
||||
loadSchema,
|
||||
} from '../DataActions';
|
||||
import gqlPattern, { gqlRelErrorNotif } from '../Common/GraphQLValidation';
|
||||
import { showErrorNotification } from '../Notification';
|
||||
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 {
|
||||
deleteRelMigrate,
|
||||
addNewRelClicked,
|
||||
@ -424,6 +500,7 @@ export {
|
||||
resetRelationshipForm,
|
||||
relManualAddClicked,
|
||||
autoTrackRelations,
|
||||
autoAddRelName,
|
||||
formRelName,
|
||||
getAllUnTrackedRelations,
|
||||
};
|
||||
|
@ -46,16 +46,13 @@ const getObjArrayRelationshipList = relationships => {
|
||||
/* This function sets the styling to the way the relationship looks, for eg: id -> user::user_id */
|
||||
const getRelationshipLine = (isObjRel, lcol, rcol, rTable) => {
|
||||
const finalRTable = rTable.name ? rTable.name : rTable;
|
||||
const getGrayText = value => <i>{value}</i>;
|
||||
return isObjRel ? (
|
||||
<span>
|
||||
{getGrayText(lcol)} → {rTable} :: {rcol}
|
||||
{lcol} → {rTable} :: {rcol}
|
||||
</span>
|
||||
) : (
|
||||
<span>
|
||||
{finalRTable} :: {rcol} → {getGrayText(
|
||||
lcol
|
||||
)}
|
||||
{finalRTable} :: {rcol} → {lcol}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
@ -777,4 +774,4 @@ const relationshipsConnector = connect =>
|
||||
|
||||
export default relationshipsConnector;
|
||||
|
||||
export { suggestedRelationships };
|
||||
export { suggestedRelationships, getRelationshipLine };
|
||||
|
Loading…
Reference in New Issue
Block a user