mirror of
https://github.com/zhigang1992/graphql-engine.git
synced 2026-05-25 10:23:36 +08:00
update track relationship text in console (#1927)
This commit is contained in:
@@ -879,15 +879,9 @@ code {
|
||||
}
|
||||
}
|
||||
|
||||
/*Newly added for remote schema page*/
|
||||
.headerText {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.addPaddRight {
|
||||
padding-right: 30px;
|
||||
}
|
||||
|
||||
.subHeaderText {
|
||||
@@ -912,13 +906,6 @@ code {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.noPadd {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
/*Newly added for remote schema page*/
|
||||
|
||||
.heading_text {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
|
||||
@@ -16,7 +16,50 @@ class CustomResolver extends React.Component {
|
||||
|
||||
const { dispatch, migrationMode, customResolverList } = this.props;
|
||||
|
||||
const showFirstSection = customResolverList.resolvers.length ? false : true;
|
||||
const getIntroSection = () => {
|
||||
const showIntroSection = !customResolverList.resolvers.length;
|
||||
if (!showIntroSection) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<TopicDescription
|
||||
title="What are Remote Schemas?"
|
||||
imgUrl="https://storage.googleapis.com/hasura-graphql-engine/console/assets/remote_schema.png"
|
||||
imgAlt="Remote Schema"
|
||||
description="Remote schemas are external GraphQL services which can be merged with Hasura to provide a unified GraphQL API. Think of it like automated schema stitching. All you need to do is build a GraphQL service and then provide its HTTP endpoint to Hasura. Your GraphQL service can be written in any language or framework."
|
||||
/>
|
||||
<hr className={styles.clear_fix} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const getAddBtn = () => {
|
||||
let addBtn = null;
|
||||
|
||||
if (migrationMode) {
|
||||
const handleClick = e => {
|
||||
e.preventDefault();
|
||||
|
||||
dispatch(push(`${globals.urlPrefix}${appPrefix}/manage/add`));
|
||||
};
|
||||
|
||||
addBtn = (
|
||||
<Button
|
||||
data-test="data-create-remote-schemas"
|
||||
color="yellow"
|
||||
size="sm"
|
||||
className={styles.add_mar_left}
|
||||
onClick={handleClick}
|
||||
>
|
||||
Add
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
return addBtn;
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -28,41 +71,14 @@ class CustomResolver extends React.Component {
|
||||
<Helmet title={`${pageTitle}s | Hasura`} />
|
||||
<div>
|
||||
<div className={styles.display_flex}>
|
||||
<h2
|
||||
className={`${styles.headerText} ${styles.addPaddRight} ${
|
||||
styles.inline_block
|
||||
}`}
|
||||
>
|
||||
<h2 className={`${styles.headerText} ${styles.inline_block}`}>
|
||||
Remote Schemas
|
||||
</h2>
|
||||
{migrationMode ? (
|
||||
<Button
|
||||
data-test="data-create-remote-schemas"
|
||||
color="yellow"
|
||||
size="sm"
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
dispatch(
|
||||
push(`${globals.urlPrefix}${appPrefix}/manage/add`)
|
||||
);
|
||||
}}
|
||||
>
|
||||
Add
|
||||
</Button>
|
||||
) : null}
|
||||
{getAddBtn()}
|
||||
</div>
|
||||
<hr />
|
||||
{showFirstSection ? (
|
||||
<div>
|
||||
<TopicDescription
|
||||
title="What are Remote Schemas?"
|
||||
imgUrl="https://storage.googleapis.com/hasura-graphql-engine/console/assets/remote_schema.png"
|
||||
imgAlt="Remote Schema"
|
||||
description="Remote schemas are external GraphQL services which can be merged with Hasura to provide a unified GraphQL API. Think of it like automated schema stitching. All you need to do is build a GraphQL service and then provide its HTTP endpoint to Hasura. Your GraphQL service can be written in any language or framework."
|
||||
/>
|
||||
<hr className={styles.clear_fix} />
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{getIntroSection()}
|
||||
|
||||
<TryItOut
|
||||
service="remoteSchema"
|
||||
@@ -76,34 +92,6 @@ class CustomResolver extends React.Component {
|
||||
adMoreLink="https://github.com/hasura/graphql-engine/tree/master/community/boilerplates/remote-schemas/"
|
||||
/>
|
||||
</div>
|
||||
{/*
|
||||
<div className={styles.resolverContent}>
|
||||
Add pre-CRUD custom business logic like data validation, etc. or also
|
||||
fetch data from another GraphQL server by stitching schemas
|
||||
</div>
|
||||
<div className={styles.resolverImg}>
|
||||
<img src={landingImage} />
|
||||
</div>
|
||||
<div className={styles.commonBtn}>
|
||||
<Link
|
||||
className={styles.padd_remove_full}
|
||||
to={`${appPrefix}/manage/add`}
|
||||
>
|
||||
<button className={styles.yellow_button}>
|
||||
Add Remote GraphQL schema
|
||||
</button>
|
||||
</Link>
|
||||
</div>
|
||||
<div className={styles.readMore}>
|
||||
<a
|
||||
href="https://docs.hasura.io/1.0/graphql/manual/schema/custom-logic/index.html"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Read more
|
||||
</a>
|
||||
</div>
|
||||
*/}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,107 +0,0 @@
|
||||
/* eslint-disable space-infix-ops */
|
||||
/* eslint-disable no-loop-func */
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import {
|
||||
autoTrackRelations,
|
||||
autoAddRelName,
|
||||
} from '../TableRelationships/Actions';
|
||||
import { getRelationshipLine } from '../TableRelationships/Relationships';
|
||||
import Button from '../../../Common/Button/Button';
|
||||
|
||||
class AutoAddRelations extends Component {
|
||||
trackAllRelations = untrackedData => {
|
||||
this.props.dispatch(autoTrackRelations(untrackedData));
|
||||
};
|
||||
|
||||
render() {
|
||||
const styles = require('../../../Common/Layout/LeftSubSidebar/LeftSubSidebar.scss');
|
||||
|
||||
const { untrackedRelations, dispatch } = this.props;
|
||||
|
||||
const handleAutoAddIndivRel = obj => {
|
||||
dispatch(autoAddRelName(obj));
|
||||
};
|
||||
|
||||
if (untrackedRelations.length === 0) {
|
||||
return (
|
||||
<div
|
||||
className={styles.display_inline + ' ' + styles.padd_bottom}
|
||||
key="no-untracked-rel"
|
||||
>
|
||||
There are no untracked relations
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const untrackData = untrackedRelations.map((obj, i) => {
|
||||
return (
|
||||
<div
|
||||
className={styles.padd_top_medium}
|
||||
key={`${obj.data.tableName}-${obj.data.rTable}-${i}`}
|
||||
>
|
||||
<Button
|
||||
className={`${styles.display_inline}`}
|
||||
size="xs"
|
||||
color="white"
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
handleAutoAddIndivRel(obj);
|
||||
}}
|
||||
>
|
||||
Add
|
||||
</Button>
|
||||
<div className={styles.display_inline + ' ' + styles.add_pad_left}>
|
||||
<b>{obj.data.tableName}</b> -{' '}
|
||||
{getRelationshipLine(
|
||||
obj.data.isObjRel,
|
||||
obj.data.lcol,
|
||||
obj.data.rcol,
|
||||
obj.data.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.bind(this, untrackedRelations)}
|
||||
className={`${styles.display_inline} ${styles.add_mar_left}`}
|
||||
color="white"
|
||||
size="xs"
|
||||
data-test="track-all-relationships"
|
||||
>
|
||||
Track All Relations
|
||||
</Button>
|
||||
<div className={styles.padd_top_small}>{untrackData}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
AutoAddRelations.propTypes = {
|
||||
untrackedRelations: PropTypes.array.isRequired,
|
||||
schema: PropTypes.array.isRequired,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default AutoAddRelations;
|
||||
@@ -28,26 +28,33 @@ import {
|
||||
LOAD_UNTRACKED_RELATIONS,
|
||||
UPDATE_CURRENT_SCHEMA,
|
||||
} from '../DataActions';
|
||||
import { getAllUnTrackedRelations } from '../TableRelationships/Actions';
|
||||
import AutoAddRelationsConnector from './AutoAddRelations';
|
||||
import {
|
||||
autoAddRelName,
|
||||
autoTrackRelations,
|
||||
getAllUnTrackedRelations,
|
||||
} from '../TableRelationships/Actions';
|
||||
import globals from '../../../../Globals';
|
||||
import { getRelDef } from '../TableRelationships/Relationships';
|
||||
|
||||
const appPrefix = globals.urlPrefix + '/data';
|
||||
|
||||
class Schema extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
isExporting: false,
|
||||
};
|
||||
|
||||
// Initialize this table
|
||||
const dispatch = this.props.dispatch;
|
||||
dispatch(fetchDataInit());
|
||||
dispatch(fetchFunctionInit());
|
||||
this.props.dispatch(fetchDataInit());
|
||||
this.props.dispatch(fetchFunctionInit());
|
||||
|
||||
const untrackedRelations = getAllUnTrackedRelations(
|
||||
this.props.schema,
|
||||
this.props.currentSchema
|
||||
).bulkRelTrack;
|
||||
|
||||
this.props.dispatch({
|
||||
type: LOAD_UNTRACKED_RELATIONS,
|
||||
untrackedRelations,
|
||||
@@ -59,6 +66,7 @@ class Schema extends Component {
|
||||
this.props.schema,
|
||||
this.props.currentSchema
|
||||
).bulkRelTrack;
|
||||
|
||||
this.props.dispatch({
|
||||
type: LOAD_UNTRACKED_RELATIONS,
|
||||
untrackedRelations,
|
||||
@@ -69,7 +77,7 @@ class Schema extends Component {
|
||||
const {
|
||||
schema,
|
||||
schemaList,
|
||||
untracked,
|
||||
untrackedTables,
|
||||
migrationMode,
|
||||
untrackedRelations,
|
||||
currentSchema,
|
||||
@@ -81,20 +89,11 @@ class Schema extends Component {
|
||||
|
||||
const styles = require('../../../Common/Layout/LeftSubSidebar/LeftSubSidebar.scss');
|
||||
|
||||
/* Filter */
|
||||
const trackedFuncs = trackedFunctions.map(t => t.function_name);
|
||||
// Assuming schema for both function and tables are same
|
||||
const trackableFuncs = functionsList.filter(f => {
|
||||
// return function which are tracked && function name whose setof tables are tracked
|
||||
return (
|
||||
trackedFuncs.indexOf(f.function_name) === -1 && !!f.return_table_info
|
||||
); // && add condition which will check whether the setoff table is tracked or not
|
||||
});
|
||||
/* */
|
||||
|
||||
const handleSchemaChange = e => {
|
||||
const updatedSchema = e.target.value;
|
||||
|
||||
dispatch(push(`${appPrefix}/schema/${updatedSchema}`));
|
||||
|
||||
Promise.all([
|
||||
dispatch({ type: UPDATE_CURRENT_SCHEMA, currentSchema: updatedSchema }),
|
||||
dispatch(fetchDataInit()),
|
||||
@@ -103,144 +102,271 @@ class Schema extends Component {
|
||||
]);
|
||||
};
|
||||
|
||||
let relationships = 0;
|
||||
schema.map(t => (relationships += t.relationships.length));
|
||||
/***********/
|
||||
|
||||
// find which tables are untracked
|
||||
const ids1 = schema.map(item => item.table_name);
|
||||
const ids2 = untracked.map(item => item.table_name);
|
||||
const getTrackableFunctions = () => {
|
||||
const trackedFuncNames = trackedFunctions.map(t => t.function_name);
|
||||
|
||||
const untrackedTables = ids1
|
||||
.map((id, index) => {
|
||||
if (ids2.indexOf(id) < 0) {
|
||||
return schema[index];
|
||||
}
|
||||
})
|
||||
.concat(
|
||||
ids2.map((id, index) => {
|
||||
if (ids1.indexOf(id) < 0) {
|
||||
return untracked[index];
|
||||
}
|
||||
})
|
||||
)
|
||||
.filter(item => item !== undefined)
|
||||
.sort((a, b) => {
|
||||
// Assuming schema for both function and tables are same
|
||||
// return function which are tracked && function name whose
|
||||
// set of tables are tracked
|
||||
const filterCondition = func => {
|
||||
return (
|
||||
!trackedFuncNames.includes(func.function_name) &&
|
||||
!!func.return_table_info
|
||||
);
|
||||
};
|
||||
|
||||
return functionsList.filter(filterCondition);
|
||||
};
|
||||
|
||||
const getUntrackedTables = () => {
|
||||
const tableNames = schema.map(item => item.table_name);
|
||||
const untrackedTableNames = untrackedTables.map(item => item.table_name);
|
||||
|
||||
const schemaUntrackedTables = schema.filter(
|
||||
table => !untrackedTableNames.includes(table.table_name)
|
||||
);
|
||||
|
||||
const untrackedTablesNotInSchema = untrackedTables.filter(
|
||||
table => !tableNames.includes(table.table_name)
|
||||
);
|
||||
|
||||
const tableSortFunc = (a, b) => {
|
||||
return a.table_name === b.table_name
|
||||
? 0
|
||||
: +(a.table_name > b.table_name) || -1;
|
||||
};
|
||||
|
||||
const _untrackedTables = schemaUntrackedTables.concat(
|
||||
untrackedTablesNotInSchema
|
||||
);
|
||||
|
||||
return _untrackedTables.sort(tableSortFunc);
|
||||
};
|
||||
|
||||
/***********/
|
||||
|
||||
const allUntrackedTables = getUntrackedTables();
|
||||
const trackableFuncs = getTrackableFunctions();
|
||||
|
||||
const getCreateBtn = () => {
|
||||
let createBtn = null;
|
||||
|
||||
if (migrationMode) {
|
||||
const handleClick = e => {
|
||||
e.preventDefault();
|
||||
|
||||
dispatch(push(`${appPrefix}/schema/${currentSchema}/table/add`));
|
||||
};
|
||||
|
||||
createBtn = (
|
||||
<Button
|
||||
data-test="data-create-table"
|
||||
color="yellow"
|
||||
size="sm"
|
||||
className={styles.add_mar_left}
|
||||
onClick={handleClick}
|
||||
>
|
||||
Create Table
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
return createBtn;
|
||||
};
|
||||
|
||||
const getCurrentSchemaSection = () => {
|
||||
const schemaOptions = schemaList.map(s => {
|
||||
return <option key={s.schema_name}>{s.schema_name}</option>;
|
||||
});
|
||||
|
||||
const untrackedHtml = [];
|
||||
for (let i = 0; i < untrackedTables.length; i++) {
|
||||
untrackedHtml.push(
|
||||
<div className={styles.padd_bottom} key={`${i}untracked`}>
|
||||
<div className={`${styles.display_inline} ${styles.padd_right}`}>
|
||||
return (
|
||||
<div className={styles.add_mar_top}>
|
||||
<div className={styles.display_inline}>Current Postgres schema</div>
|
||||
<div className={styles.display_inline}>
|
||||
<select
|
||||
onChange={handleSchemaChange}
|
||||
className={styles.changeSchema + ' form-control'}
|
||||
value={currentSchema}
|
||||
>
|
||||
{schemaOptions}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const getUntrackedTablesSection = () => {
|
||||
const getTrackAllBtn = () => {
|
||||
let trackAllBtn = null;
|
||||
|
||||
if (allUntrackedTables.length > 0) {
|
||||
trackAllBtn = (
|
||||
<Button
|
||||
data-test={`add-track-table-${untrackedTables[i].table_name}`}
|
||||
className={`${styles.display_inline}`}
|
||||
className={`${styles.display_inline} ${styles.addAllBtn}`}
|
||||
color="white"
|
||||
size="xs"
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
dispatch(setTableName(untrackedTables[i].table_name));
|
||||
dispatch(addExistingTableSql());
|
||||
dispatch(addAllUntrackedTablesSql(allUntrackedTables));
|
||||
}}
|
||||
>
|
||||
Add
|
||||
Track All
|
||||
</Button>
|
||||
</div>
|
||||
<div className={`${styles.padd_right} ${styles.inline_block}`}>
|
||||
{untrackedTables[i].table_name}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (!untrackedHtml.length) {
|
||||
untrackedHtml.push(
|
||||
<div key="no-untracked">There are no untracked tables or views</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`${styles.padd_left_remove} container-fluid ${
|
||||
styles.padd_top
|
||||
}`}
|
||||
>
|
||||
<div className={styles.padd_left}>
|
||||
<Helmet title="Schema - Data | Hasura" />
|
||||
<div>
|
||||
<h2 className={`${styles.heading_text} ${styles.inline_block}`}>
|
||||
{' '}
|
||||
Schema{' '}
|
||||
</h2>
|
||||
{migrationMode ? (
|
||||
<Button
|
||||
data-test="data-create-table"
|
||||
color="yellow"
|
||||
size="sm"
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
dispatch(
|
||||
push(`${appPrefix}/schema/${currentSchema}/table/add`)
|
||||
);
|
||||
}}
|
||||
>
|
||||
Create Table
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
<hr />
|
||||
<div>
|
||||
<div className={styles.display_inline}>Current postgres schema</div>
|
||||
<div className={styles.display_inline}>
|
||||
<select
|
||||
onChange={handleSchemaChange}
|
||||
className={styles.changeSchema + ' form-control'}
|
||||
value={currentSchema}
|
||||
>
|
||||
{schemaList.map(s => {
|
||||
if (s.schema_name === currentSchema) {
|
||||
return <option key={s.schema_name}>{s.schema_name}</option>;
|
||||
}
|
||||
return <option key={s.schema_name}>{s.schema_name}</option>;
|
||||
})}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
<div className={styles.add_pad_bottom}>
|
||||
<div>
|
||||
<h4
|
||||
className={`${styles.subheading_text} ${
|
||||
styles.heading_tooltip
|
||||
}`}
|
||||
>
|
||||
Untracked tables or views
|
||||
</h4>
|
||||
<OverlayTrigger placement="right" overlay={untrackedTip}>
|
||||
<i className="fa fa-info-circle" aria-hidden="true" />
|
||||
</OverlayTrigger>
|
||||
{untrackedTables.length > 0 ? (
|
||||
return trackAllBtn;
|
||||
};
|
||||
|
||||
const getUntrackedTablesList = () => {
|
||||
const untrackedTablesList = [];
|
||||
|
||||
allUntrackedTables.forEach((table, i) => {
|
||||
const handleTrackTable = e => {
|
||||
e.preventDefault();
|
||||
|
||||
dispatch(setTableName(table.table_name));
|
||||
dispatch(addExistingTableSql());
|
||||
};
|
||||
|
||||
untrackedTablesList.push(
|
||||
<div className={styles.padd_bottom} key={`untracked-${i}`}>
|
||||
<div className={styles.inline_block}>
|
||||
<Button
|
||||
className={`${styles.display_inline} ${styles.addAllBtn}`}
|
||||
data-test={`add-track-table-${table.table_name}`}
|
||||
className={`${styles.display_inline}`}
|
||||
color="white"
|
||||
size="xs"
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
dispatch(addAllUntrackedTablesSql(untrackedTables));
|
||||
}}
|
||||
onClick={handleTrackTable}
|
||||
>
|
||||
Add all
|
||||
Track
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
<div className={`${styles.padd_left_remove} col-xs-12`}>
|
||||
{untrackedHtml}
|
||||
</div>
|
||||
<div className={styles.inline_block}>{table.table_name}</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
if (!untrackedTablesList.length) {
|
||||
untrackedTablesList.push(
|
||||
<div key="no-untracked">There are no untracked tables or views</div>
|
||||
);
|
||||
}
|
||||
|
||||
return untrackedTablesList;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.add_mar_top}>
|
||||
<div>
|
||||
<h4
|
||||
className={`${styles.subheading_text} ${styles.heading_tooltip}`}
|
||||
>
|
||||
Untracked tables or views
|
||||
</h4>
|
||||
<OverlayTrigger placement="right" overlay={untrackedTip}>
|
||||
<i className="fa fa-info-circle" aria-hidden="true" />
|
||||
</OverlayTrigger>
|
||||
{getTrackAllBtn()}
|
||||
</div>
|
||||
<hr />
|
||||
<div className={styles.wd100 + ' ' + styles.clear_fix}>
|
||||
<div className={`${styles.padd_left_remove} col-xs-12`}>
|
||||
{getUntrackedTablesList()}
|
||||
</div>
|
||||
<div className={styles.clear_fix} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const getUntrackedRelationsSection = () => {
|
||||
const getTrackAllBtn = () => {
|
||||
let trackAllBtn = null;
|
||||
|
||||
const trackAllRelations = () => {
|
||||
this.props.dispatch(autoTrackRelations(untrackedRelations));
|
||||
};
|
||||
|
||||
if (untrackedRelations.length > 0) {
|
||||
trackAllBtn = (
|
||||
<Button
|
||||
onClick={trackAllRelations}
|
||||
className={`${styles.display_inline} ${styles.add_mar_left}`}
|
||||
color="white"
|
||||
size="xs"
|
||||
data-test="track-all-relationships"
|
||||
>
|
||||
Track All
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
return trackAllBtn;
|
||||
};
|
||||
|
||||
const getUntrackedRelList = () => {
|
||||
const untrackedRelList = [];
|
||||
|
||||
untrackedRelations.forEach((rel, i) => {
|
||||
const relData = rel.data;
|
||||
|
||||
const handleAddRel = e => {
|
||||
e.preventDefault();
|
||||
|
||||
dispatch(autoAddRelName(rel));
|
||||
};
|
||||
|
||||
const relFrom = <b>{relData.tableName}</b>;
|
||||
|
||||
const relTo = relData.isObjRel ? (
|
||||
<b>{relData.rTable}</b>
|
||||
) : (
|
||||
<b>[ {relData.rTable} ]</b>
|
||||
);
|
||||
|
||||
untrackedRelList.push(
|
||||
<div className={styles.padd_bottom} key={`untracked-rel-${i}`}>
|
||||
<div className={styles.inline_block}>
|
||||
<Button
|
||||
className={styles.display_inline}
|
||||
color="white"
|
||||
size="xs"
|
||||
onClick={handleAddRel}
|
||||
>
|
||||
Track
|
||||
</Button>
|
||||
</div>
|
||||
<div className={styles.inline_block}>
|
||||
<span>
|
||||
{relFrom} → {relTo}
|
||||
</span>
|
||||
-
|
||||
<span>
|
||||
{getRelDef(
|
||||
relData.isObjRel,
|
||||
relData.lcol,
|
||||
relData.rcol,
|
||||
relData.tableName,
|
||||
relData.rTable
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
if (!untrackedRelList.length) {
|
||||
untrackedRelList.push(
|
||||
<div key="no-untracked-rel">There are no untracked relations</div>
|
||||
);
|
||||
}
|
||||
|
||||
return untrackedRelList;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.add_mar_top}>
|
||||
<div>
|
||||
<h4
|
||||
className={`${styles.subheading_text} ${styles.heading_tooltip}`}
|
||||
>
|
||||
@@ -249,120 +375,139 @@ class Schema extends Component {
|
||||
<OverlayTrigger placement="right" overlay={untrackedRelTip}>
|
||||
<i className="fa fa-info-circle" aria-hidden="true" />
|
||||
</OverlayTrigger>
|
||||
<div className={`${styles.padd_left_remove} col-xs-12`}>
|
||||
<div>
|
||||
<AutoAddRelationsConnector
|
||||
untrackedRelations={untrackedRelations}
|
||||
schema={schema}
|
||||
dispatch={dispatch}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{getTrackAllBtn()}
|
||||
</div>
|
||||
<div className={`${styles.padd_left_remove} col-xs-12`}>
|
||||
{getUntrackedRelList()}
|
||||
</div>
|
||||
<div className={styles.clear_fix} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
{trackableFuncs.length > 0
|
||||
? [
|
||||
<hr
|
||||
className={styles.wd100 + ' ' + styles.clear_fix}
|
||||
key={'custom-functions-hr'}
|
||||
/>,
|
||||
<div
|
||||
className={styles.wd100 + ' ' + styles.clear_fix}
|
||||
key={'custom-functions-content'}
|
||||
>
|
||||
<h4
|
||||
className={`${styles.subheading_text} ${
|
||||
styles.heading_tooltip
|
||||
}`}
|
||||
>
|
||||
Untracked custom functions
|
||||
</h4>
|
||||
<OverlayTrigger
|
||||
placement="right"
|
||||
overlay={trackableFunctions}
|
||||
>
|
||||
<i className="fa fa-info-circle" aria-hidden="true" />
|
||||
</OverlayTrigger>
|
||||
<div className={`${styles.padd_left_remove} col-xs-12`}>
|
||||
{trackableFuncs.map((p, i) => (
|
||||
<div
|
||||
className={styles.padd_bottom}
|
||||
key={`${i}untracked-function`}
|
||||
>
|
||||
<div
|
||||
className={`${styles.display_inline} ${
|
||||
styles.padd_right
|
||||
}`}
|
||||
>
|
||||
<Button
|
||||
data-test={`add-track-function-${p.function_name}`}
|
||||
className={`${
|
||||
styles.display_inline
|
||||
} btn btn-xs btn-default`}
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
dispatch(addExistingFunction(p.function_name));
|
||||
}}
|
||||
>
|
||||
Add
|
||||
</Button>
|
||||
</div>
|
||||
<div
|
||||
className={`${styles.padd_right} ${
|
||||
styles.inline_block
|
||||
}`}
|
||||
>
|
||||
{p.function_name}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>,
|
||||
]
|
||||
: null}
|
||||
const getUntrackedFunctionsSection = () => {
|
||||
let trackableFunctionList = null;
|
||||
|
||||
{/* nonTrackableFunctionsList.length > 0
|
||||
? [
|
||||
<hr
|
||||
className={styles.wd100 + ' ' + styles.clear_fix}
|
||||
key={'non-trackable-custom-functions-id'}
|
||||
/>,
|
||||
<div
|
||||
className={styles.wd100 + ' ' + styles.clear_fix}
|
||||
key={'non-trackable-custom-functions-content'}
|
||||
if (trackableFuncs.length > 0) {
|
||||
trackableFunctionList = (
|
||||
<div className={styles.add_mar_top} key={'custom-functions-content'}>
|
||||
<div>
|
||||
<h4
|
||||
className={`${styles.subheading_text} ${
|
||||
styles.heading_tooltip
|
||||
}`}
|
||||
>
|
||||
<h4
|
||||
className={`${styles.subheading_text} ${
|
||||
styles.heading_tooltip
|
||||
}`}
|
||||
Untracked custom functions
|
||||
</h4>
|
||||
<OverlayTrigger placement="right" overlay={trackableFunctions}>
|
||||
<i className="fa fa-info-circle" aria-hidden="true" />
|
||||
</OverlayTrigger>
|
||||
</div>
|
||||
<div className={`${styles.padd_left_remove} col-xs-12`}>
|
||||
{trackableFuncs.map((p, i) => (
|
||||
<div
|
||||
className={styles.padd_bottom}
|
||||
key={`${i}untracked-function`}
|
||||
>
|
||||
Non trackable custom functions
|
||||
</h4>
|
||||
<OverlayTrigger
|
||||
placement="right"
|
||||
overlay={nonTrackableFunctions}
|
||||
>
|
||||
<i className="fa fa-info-circle" aria-hidden="true" />
|
||||
</OverlayTrigger>
|
||||
<div className={`${styles.padd_left_remove} col-xs-12`}>
|
||||
{nonTrackableFunctionsList.map((p, i) => (
|
||||
<div
|
||||
className={styles.padd_bottom}
|
||||
key={`${i}untracked-function`}
|
||||
<div
|
||||
className={`${styles.display_inline} ${styles.padd_right}`}
|
||||
>
|
||||
<Button
|
||||
data-test={`add-track-function-${p.function_name}`}
|
||||
className={`${
|
||||
styles.display_inline
|
||||
} btn btn-xs btn-default`}
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
dispatch(addExistingFunction(p.function_name));
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className={`${styles.padd_right} ${
|
||||
styles.inline_block
|
||||
}`}
|
||||
>
|
||||
{p.function_name}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
Track
|
||||
</Button>
|
||||
</div>
|
||||
<div
|
||||
className={`${styles.padd_right} ${styles.inline_block}`}
|
||||
>
|
||||
{p.function_name}
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
]
|
||||
: null */}
|
||||
))}
|
||||
</div>
|
||||
<div className={styles.clear_fix} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return trackableFunctionList;
|
||||
};
|
||||
|
||||
const getNonTrackableFunctionsSection = () => {
|
||||
const nonTrackableFuncList = null;
|
||||
|
||||
// if (nonTrackableFunctionsList.length > 0) {
|
||||
// nonTrackableFuncList = (
|
||||
// <div
|
||||
// className={styles.add_mar_top}
|
||||
// key={'non-trackable-custom-functions-content'}
|
||||
// >
|
||||
// <div>
|
||||
// <h4
|
||||
// className={`${styles.subheading_text} ${
|
||||
// styles.heading_tooltip
|
||||
// }`}
|
||||
// >
|
||||
// Non trackable custom functions
|
||||
// </h4>
|
||||
// <OverlayTrigger
|
||||
// placement="right"
|
||||
// overlay={nonTrackableFunctions}
|
||||
// >
|
||||
// <i className="fa fa-info-circle" aria-hidden="true" />
|
||||
// </OverlayTrigger>
|
||||
// </div>
|
||||
// <div className={`${styles.padd_left_remove} col-xs-12`}>
|
||||
// {nonTrackableFunctionsList.map((p, i) => (
|
||||
// <div
|
||||
// className={styles.padd_bottom}
|
||||
// key={`${i}untracked-function`}
|
||||
// >
|
||||
// <div
|
||||
// className={`${styles.padd_right} ${
|
||||
// styles.inline_block
|
||||
// }`}
|
||||
// >
|
||||
// {p.function_name}
|
||||
// </div>
|
||||
// </div>
|
||||
// ))}
|
||||
// </div>
|
||||
// <div className={styles.clear_fix} />
|
||||
// </div>
|
||||
// );
|
||||
// }
|
||||
|
||||
return nonTrackableFuncList;
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`container-fluid ${styles.padd_left_remove} ${
|
||||
styles.padd_top
|
||||
}`}
|
||||
>
|
||||
<div className={styles.padd_left}>
|
||||
<Helmet title="Schema - Data | Hasura" />
|
||||
<div className={styles.display_flex}>
|
||||
<h2 className={`${styles.headerText} ${styles.inline_block}`}>
|
||||
Schema
|
||||
</h2>
|
||||
{getCreateBtn()}
|
||||
</div>
|
||||
{getCurrentSchemaSection()}
|
||||
{getUntrackedTablesSection()}
|
||||
{getUntrackedRelationsSection()}
|
||||
{getUntrackedFunctionsSection()}
|
||||
{getNonTrackableFunctionsSection()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -371,7 +516,7 @@ class Schema extends Component {
|
||||
|
||||
Schema.propTypes = {
|
||||
schema: PropTypes.array.isRequired,
|
||||
untracked: PropTypes.array.isRequired,
|
||||
untrackedTables: PropTypes.array.isRequired,
|
||||
untrackedRelations: PropTypes.array.isRequired,
|
||||
migrationMode: PropTypes.bool.isRequired,
|
||||
currentSchema: PropTypes.string.isRequired,
|
||||
@@ -381,7 +526,7 @@ Schema.propTypes = {
|
||||
const mapStateToProps = state => ({
|
||||
schema: state.tables.allSchemas,
|
||||
schemaList: state.tables.schemaList,
|
||||
untracked: state.tables.untrackedSchemas,
|
||||
untrackedTables: state.tables.untrackedSchemas,
|
||||
migrationMode: state.main.migrationMode,
|
||||
untrackedRelations: state.tables.untrackedRelations,
|
||||
currentSchema: state.tables.currentSchema,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* eslint-disable jsx-a11y/no-autofocus */
|
||||
import React from 'react';
|
||||
import { getRelationshipLine } from './utils';
|
||||
import { getRelDef } from './utils';
|
||||
import Button from '../../../Common/Button/Button';
|
||||
import { deleteRelMigrate, saveRenameRelationship } from './Actions';
|
||||
import { showErrorNotification } from '../Notification';
|
||||
@@ -66,11 +66,14 @@ class RelationshipEditor extends React.Component {
|
||||
relName,
|
||||
relConfig,
|
||||
isObjRel,
|
||||
tableStyles,
|
||||
allowRename,
|
||||
} = this.props;
|
||||
|
||||
const { text, isEditting } = this.state;
|
||||
const { lcol, rtable, rcol } = relConfig;
|
||||
|
||||
const tableStyles = require('../../../Common/TableCommon/TableStyles.scss');
|
||||
|
||||
const onDelete = e => {
|
||||
e.preventDefault();
|
||||
const isOk = confirm('Are you sure?');
|
||||
@@ -97,7 +100,7 @@ class RelationshipEditor extends React.Component {
|
||||
|
||||
<b>{relName}</b>
|
||||
<div className={tableStyles.relationshipTopPadding}>
|
||||
{getRelationshipLine(isObjRel, lcol, rcol, rtable)}
|
||||
{getRelDef(isObjRel, lcol, rcol, tableName, rtable)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -115,7 +118,7 @@ class RelationshipEditor extends React.Component {
|
||||
</Button>
|
||||
</div>
|
||||
<div className={tableStyles.relationshipTopPadding}>
|
||||
<div>{getRelationshipLine(isObjRel, lcol, rcol, rtable)}</div>
|
||||
<div>{getRelDef(isObjRel, lcol, rcol, tableName, rtable)}</div>
|
||||
<input
|
||||
onChange={this.handleTextChange}
|
||||
className={`form-control ${styles.add_mar_top_small}`}
|
||||
|
||||
@@ -16,7 +16,7 @@ import { findAllFromRel } from '../utils';
|
||||
import { showErrorNotification } from '../Notification';
|
||||
import { setTable } from '../DataActions';
|
||||
import gqlPattern, { gqlRelErrorNotif } from '../Common/GraphQLValidation';
|
||||
import { getRelationshipLine } from './utils';
|
||||
import { getRelDef } from './utils';
|
||||
|
||||
import AddManualRelationship from './AddManualRelationship';
|
||||
import suggestedRelationshipsRaw from './autoRelations';
|
||||
@@ -51,10 +51,11 @@ const addRelationshipCellView = (
|
||||
rel,
|
||||
selectedRelationship,
|
||||
selectedRelationshipName,
|
||||
tableStyles,
|
||||
relMetaData,
|
||||
tableSchema
|
||||
) => {
|
||||
const tableStyles = require('../../../Common/TableCommon/TableStyles.scss');
|
||||
|
||||
const onAdd = e => {
|
||||
e.preventDefault();
|
||||
dispatch(relSelectionChanged(rel));
|
||||
@@ -109,7 +110,14 @@ const addRelationshipCellView = (
|
||||
Add
|
||||
</Button>
|
||||
)}
|
||||
{getRelationshipLine(rel.isObjRel, rel.lcol, rel.rcol, rel.rTable)}{' '}
|
||||
|
||||
{getRelDef(
|
||||
rel.isObjRel,
|
||||
rel.lcol,
|
||||
rel.rcol,
|
||||
tableSchema.table_name,
|
||||
rel.rTable
|
||||
)}{' '}
|
||||
|
||||
</div>
|
||||
{selectedRelationship === rel ? (
|
||||
@@ -148,15 +156,17 @@ const AddRelationship = ({
|
||||
allSchemas,
|
||||
cachedRelationshipData,
|
||||
dispatch,
|
||||
tableStyles,
|
||||
}) => {
|
||||
const styles = require('../TableModify/ModifyTable.scss');
|
||||
const tableStyles = require('../../../Common/TableCommon/TableStyles.scss');
|
||||
|
||||
const cTable = allSchemas.find(t => t.table_name === tableName);
|
||||
|
||||
const suggestedRelationshipsData = suggestedRelationshipsRaw(
|
||||
tableName,
|
||||
allSchemas
|
||||
);
|
||||
const styles = require('../TableModify/ModifyTable.scss');
|
||||
|
||||
if (
|
||||
suggestedRelationshipsData.objectRel.length < 1 &&
|
||||
suggestedRelationshipsData.arrayRel.length < 1
|
||||
@@ -231,7 +241,6 @@ const AddRelationship = ({
|
||||
rel,
|
||||
selectedRelationship,
|
||||
relName,
|
||||
tableStyles,
|
||||
['object', i],
|
||||
cTable
|
||||
)
|
||||
@@ -251,7 +260,6 @@ const AddRelationship = ({
|
||||
rel,
|
||||
selectedRelationship,
|
||||
relName,
|
||||
tableStyles,
|
||||
['array', i],
|
||||
cTable
|
||||
)
|
||||
@@ -429,7 +437,6 @@ class Relationships extends Component {
|
||||
rel.objRel
|
||||
)}
|
||||
isObjRel
|
||||
tableStyles={tableStyles}
|
||||
allowRename={this.state.supportRename}
|
||||
/>
|
||||
) : (
|
||||
@@ -447,7 +454,6 @@ class Relationships extends Component {
|
||||
rel.arrRel
|
||||
)}
|
||||
isObjRel={false}
|
||||
tableStyles={tableStyles}
|
||||
allowRename={this.state.supportRename}
|
||||
/>
|
||||
) : (
|
||||
@@ -488,7 +494,6 @@ class Relationships extends Component {
|
||||
tableName={tableName}
|
||||
allSchemas={allSchemas}
|
||||
cachedRelationshipData={relAdd}
|
||||
tableStyles={tableStyles}
|
||||
dispatch={dispatch}
|
||||
/>
|
||||
</div>
|
||||
@@ -581,4 +586,4 @@ const relationshipsConnector = connect =>
|
||||
|
||||
export default relationshipsConnector;
|
||||
|
||||
export { getRelationshipLine };
|
||||
export { getRelDef };
|
||||
|
||||
@@ -133,7 +133,7 @@ class RelationshipsView extends Component {
|
||||
</thead>
|
||||
<tbody>
|
||||
{getObjArrayRelationshipList(tableSchema.relationships).map(
|
||||
(rel, i) => {
|
||||
rel => {
|
||||
const column1 = rel.objRel ? (
|
||||
<RelationshipEditor
|
||||
dispatch={dispatch}
|
||||
@@ -146,7 +146,6 @@ class RelationshipsView extends Component {
|
||||
rel.objRel
|
||||
)}
|
||||
isObjRel
|
||||
tableStyles={tableStyles}
|
||||
allowRename={this.state.supportRename}
|
||||
/>
|
||||
) : (
|
||||
@@ -164,7 +163,6 @@ class RelationshipsView extends Component {
|
||||
rel.arrRel
|
||||
)}
|
||||
isObjRel={false}
|
||||
tableStyles={tableStyles}
|
||||
allowRename={this.state.supportRename}
|
||||
/>
|
||||
) : (
|
||||
|
||||
@@ -1,21 +1,20 @@
|
||||
import React from 'react';
|
||||
|
||||
/* This function sets the styling to the way the relationship looks, for eg: id -> user::user_id */
|
||||
export const getRelationshipLine = (isObjRel, lcol, rcol, rTable) => {
|
||||
const finalRTable = rTable.name ? rTable.name : rTable;
|
||||
/* This function sets the styling to the way the relationship looks, for eg: article.id -> user.user_id */
|
||||
export const getRelDef = (isObjRel, lcol, rcol, lTable, _rTable) => {
|
||||
const rTable = _rTable.name ? _rTable.name : _rTable;
|
||||
|
||||
return isObjRel ? (
|
||||
<span>
|
||||
|
||||
{lcol.join(',')}
|
||||
→
|
||||
{rTable} :: {rcol.join(',')}
|
||||
{lTable} . {lcol.join(',')}
|
||||
→
|
||||
{_rTable} . {rcol.join(',')}
|
||||
</span>
|
||||
) : (
|
||||
<span>
|
||||
|
||||
{finalRTable} :: {rcol.join(',')}
|
||||
→
|
||||
{lcol.join(',')}
|
||||
{rTable} . {rcol.join(',')}
|
||||
→
|
||||
{lTable} . {lcol.join(',')}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -27,18 +27,64 @@ class Schema extends Component {
|
||||
|
||||
const styles = require('../../../Common/Layout/LeftSubSidebar/LeftSubSidebar.scss');
|
||||
|
||||
const showFirstSection = listingTrigger.length ? false : true;
|
||||
const queryDefinition = `mutation {
|
||||
insert_user(objects: [{name: "testuser"}] ){
|
||||
affected_rows
|
||||
}
|
||||
}`;
|
||||
|
||||
const getIntroSection = () => {
|
||||
const showIntroSection = !listingTrigger.length;
|
||||
if (!showIntroSection) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<TopicDescription
|
||||
title="What are Event Triggers?"
|
||||
imgUrl="https://storage.googleapis.com/hasura-graphql-engine/console/assets/event-trigger.png"
|
||||
imgAlt="Event Triggers"
|
||||
description="Hasura can be used to create event triggers on tables. An Event Trigger atomically captures events (insert, update, delete) on a specified table and then reliably calls a webhook that can carry out any custom logic."
|
||||
/>
|
||||
<hr className={styles.clear_fix} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const getAddBtn = () => {
|
||||
let addBtn = null;
|
||||
|
||||
if (migrationMode) {
|
||||
const handleClick = e => {
|
||||
e.preventDefault();
|
||||
|
||||
dispatch(push(`${appPrefix}/manage/triggers/add`));
|
||||
};
|
||||
|
||||
addBtn = (
|
||||
<Button
|
||||
data-test="data-create-trigger"
|
||||
color="yellow"
|
||||
size="sm"
|
||||
className={styles.add_mar_left}
|
||||
onClick={handleClick}
|
||||
>
|
||||
Add
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
return addBtn;
|
||||
};
|
||||
|
||||
const footerEvent = (
|
||||
<span>
|
||||
Head to the Events tab and see an event invoked under{' '}
|
||||
<span className={styles.fontWeightBold}> test-trigger</span>.
|
||||
</span>
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`${styles.padd_left_remove} container-fluid ${
|
||||
@@ -48,39 +94,15 @@ class Schema extends Component {
|
||||
<div className={styles.padd_left}>
|
||||
<Helmet title="Event Triggers | Hasura" />
|
||||
<div className={styles.display_flex}>
|
||||
<h2
|
||||
className={`${styles.headerText} ${styles.addPaddRight} ${
|
||||
styles.inline_block
|
||||
}`}
|
||||
>
|
||||
Event Triggers{' '}
|
||||
<h2 className={`${styles.headerText} ${styles.inline_block}`}>
|
||||
Event Triggers
|
||||
</h2>
|
||||
{migrationMode ? (
|
||||
<Button
|
||||
data-test="data-create-trigger"
|
||||
color="yellow"
|
||||
size="sm"
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
dispatch(push(`${appPrefix}/manage/triggers/add`));
|
||||
}}
|
||||
>
|
||||
Add
|
||||
</Button>
|
||||
) : null}
|
||||
{getAddBtn()}
|
||||
</div>
|
||||
<hr />
|
||||
{showFirstSection ? (
|
||||
<div>
|
||||
<TopicDescription
|
||||
title="What are Event Triggers?"
|
||||
imgUrl="https://storage.googleapis.com/hasura-graphql-engine/console/assets/event-trigger.png"
|
||||
imgAlt="Event Triggers"
|
||||
description="Hasura can be used to create event triggers on tables. An Event Trigger atomically captures events (insert, update, delete) on a specified table and then reliably calls a webhook that can carry out any custom logic."
|
||||
/>
|
||||
<hr className={styles.clear_fix} />
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{getIntroSection()}
|
||||
|
||||
<TryItOut
|
||||
service="eventTrigger"
|
||||
title="Steps to deploy an example Event Trigger to Glitch"
|
||||
|
||||
@@ -21,13 +21,13 @@ To track a table or a view:
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
#. Head to the ``Data -> Schema`` section of the console.
|
||||
#. Under the heading ``Untracked Tables/Views``, click on the ``Add`` button next to the table/view name.
|
||||
#. Under the heading ``Untracked Tables/Views``, click on the ``Track`` button next to the table/view name.
|
||||
|
||||
To track all tables and views present in the database:
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
#. Head to the ``Data -> Schema`` section of the console.
|
||||
#. Under the heading ``Untracked Tables/Views``, click the ``Add all`` button.
|
||||
#. Under the heading ``Untracked Tables/Views``, click the ``Track All`` button.
|
||||
|
||||
Step 2: Track foreign-keys
|
||||
--------------------------
|
||||
@@ -50,7 +50,7 @@ To track all the foreign-keys of all tables in the database:
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
#. Head to the ``Data -> Schema`` section of the console.
|
||||
#. Under the heading ``Untracked foreign-key relations``, click on the ``Track All Relations`` to automatically
|
||||
#. Under the heading ``Untracked foreign-key relations``, click the ``Track All`` button to automatically
|
||||
create relationships based on the foreign-keys.
|
||||
|
||||
.. admonition:: Relationship nomenclature
|
||||
@@ -61,11 +61,14 @@ To track all the foreign-keys of all tables in the database:
|
||||
|
||||
The name is generated in the following format:
|
||||
|
||||
- For object relationships: ``Camel case of (foreignTableName + By + columnName)``
|
||||
- For array relationships: ``Camel case of (foreignTableName + s + By + columnNameInForeignTable)``
|
||||
- For object relationships: ``singular of foreignTableName``
|
||||
- For array relationships: ``plural of foreignTableName``
|
||||
|
||||
For example, for the foreign-key ``article::author_id -> author::id``, the relationship names will be
|
||||
``authorByAuthorId`` for ``article`` table and ``articlesByAuthorId`` for ``author`` table.
|
||||
For example, for the foreign-key ``article.author_id -> author.id``, the relationship names will be
|
||||
``author`` for ``article`` table and ``articles`` for ``author`` table.
|
||||
|
||||
In case a field with the generated name already exists, a new name will be generated of the form:
|
||||
``camel case of (singular/plural of foreignTableName + _by_ + foreignKeyColumnName)``
|
||||
|
||||
Note that, **this is just an arbitrary naming convention** chosen by Hasura to ensure generation of unique
|
||||
relationship names. You can choose to rename your relationships to anything you wish. You can **change the
|
||||
|
||||
Reference in New Issue
Block a user