mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-14 17:02:49 +03:00
console: connect database enhancements and misc fixes
Co-authored-by: Aleksandra Sikora <9019397+beerose@users.noreply.github.com> GitOrigin-RevId: a44482a1f88ad94c462b72162cbfbb35397640a3
This commit is contained in:
parent
64d52f5fa3
commit
ec79fcf52a
@ -480,6 +480,9 @@ input {
|
||||
padding-right: 0 !important;
|
||||
}
|
||||
|
||||
.add_pad_min {
|
||||
padding: 1em;
|
||||
}
|
||||
.add_pad_right {
|
||||
padding-right: 15px;
|
||||
}
|
||||
@ -595,6 +598,9 @@ code {
|
||||
.add_pad_top {
|
||||
padding-top: 20px;
|
||||
}
|
||||
.add_pad_top_10 {
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.padd_bottom {
|
||||
padding-bottom: 10px !important;
|
||||
@ -1436,14 +1442,13 @@ code {
|
||||
}
|
||||
|
||||
.db_list_item {
|
||||
display: flex;
|
||||
margin-top: 20px;
|
||||
border-bottom: 1px solid rgb(187, 187, 187);
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 1px solid rgb(232, 232, 232);
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.db_list_item:last-of-type {
|
||||
border-bottom: none;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.show_connection_string {
|
||||
@ -1452,34 +1457,20 @@ code {
|
||||
cursor: pointer;
|
||||
}
|
||||
color: #337ab7;
|
||||
p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.showAdminSecret {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.db_display_data {
|
||||
.db_large_string_break_words {
|
||||
max-width: 40%;
|
||||
word-break: break-word;
|
||||
padding: 1em;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
padding-left: 95px;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.db_list_content {
|
||||
display: inline-flex;
|
||||
width: 30%;
|
||||
}
|
||||
|
||||
.data_list_container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.db_item_actions {
|
||||
width: 14%;
|
||||
display: flex;
|
||||
margin-right: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.text_red {
|
||||
@ -1489,6 +1480,7 @@ code {
|
||||
.connect_db_content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.connect_db_header {
|
||||
@ -1511,13 +1503,12 @@ code {
|
||||
}
|
||||
|
||||
.connect_db_radio_label {
|
||||
margin-left: 4px;
|
||||
margin-right: 24px;
|
||||
}
|
||||
|
||||
.connect_form_layout {
|
||||
width: 50%;
|
||||
padding: 8px;
|
||||
padding: 8px 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-top: 10px;
|
||||
|
@ -97,9 +97,9 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-right: 1px solid #788095;
|
||||
display: inline-block;
|
||||
width: 20%;
|
||||
min-width: 240px;
|
||||
height: 54px;
|
||||
|
||||
.logoParent {
|
||||
display: flex;
|
||||
@ -1530,13 +1530,21 @@
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1250px) {
|
||||
@media (max-width: 150px) {
|
||||
.secureSectionText {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1050px) {
|
||||
@media (max-width: 1350px) {
|
||||
.sidebar {
|
||||
.header_logo_wrapper {
|
||||
padding: 0px 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1250px) {
|
||||
.sidebar {
|
||||
.sidebarItems {
|
||||
li {
|
||||
|
@ -16,7 +16,7 @@ import {
|
||||
} from './DataActions';
|
||||
import { currentDriver, useDataSource } from '../../../dataSources';
|
||||
import SourceView from './SourceView';
|
||||
import { getSourceDriver } from './utils';
|
||||
import { getSourceDriver, isInconsistentSource } from './utils';
|
||||
|
||||
type Params = {
|
||||
source?: string;
|
||||
@ -35,10 +35,19 @@ const DataSourceContainer = ({
|
||||
dispatch,
|
||||
currentSource,
|
||||
location,
|
||||
inconsistentObjects,
|
||||
}: DataSourceContainerProps) => {
|
||||
const { setDriver } = useDataSource();
|
||||
const [dataLoaded, setDataLoaded] = useState(false);
|
||||
const { source, schema } = params;
|
||||
|
||||
useEffect(() => {
|
||||
// if the source is inconsistent, do not show the source route
|
||||
if (isInconsistentSource(currentSource, inconsistentObjects)) {
|
||||
dispatch(push('/data/manage'));
|
||||
}
|
||||
}, [inconsistentObjects, currentSource, dispatch, location]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!source || source === 'undefined') {
|
||||
if (currentSource) {
|
||||
@ -109,6 +118,7 @@ const mapStateToProps = (state: ReduxState) => {
|
||||
schemaList: state.tables.schemaList,
|
||||
dataSources: getDataSources(state),
|
||||
currentSource: state.tables.currentDataSource,
|
||||
inconsistentObjects: state.metadata.inconsistentObjects,
|
||||
};
|
||||
};
|
||||
const dataSourceConnector = connect(
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { ChangeEvent } from 'react';
|
||||
import React, { ChangeEvent, FormEvent } from 'react';
|
||||
import { connect, ConnectedProps } from 'react-redux';
|
||||
|
||||
import Tabbed from './TabbedDataSourceConnection';
|
||||
@ -103,12 +103,13 @@ const ConnectDatabase: React.FC<ConnectDatabaseProps> = props => {
|
||||
};
|
||||
|
||||
const onSuccessConnectDBCb = () => {
|
||||
setLoading(false);
|
||||
resetState();
|
||||
// route to manage page
|
||||
dispatch(_push('/data/manage'));
|
||||
};
|
||||
|
||||
const onClickConnectDatabase = () => {
|
||||
const onConnectDatabase = () => {
|
||||
if (!connectDBInputState.displayName.trim()) {
|
||||
dispatch(
|
||||
showErrorNotification(
|
||||
@ -204,10 +205,14 @@ const ConnectDatabase: React.FC<ConnectDatabaseProps> = props => {
|
||||
.then(() => setLoading(false))
|
||||
.catch(() => setLoading(false));
|
||||
};
|
||||
const onSubmit = (e: FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
onConnectDatabase();
|
||||
};
|
||||
|
||||
return (
|
||||
<Tabbed tabName="connect">
|
||||
<div className={styles.connect_db_content}>
|
||||
<form onSubmit={onSubmit} className={styles.connect_db_content}>
|
||||
<h4
|
||||
className={`${styles.remove_pad_bottom} ${styles.connect_db_header}`}
|
||||
>
|
||||
@ -223,7 +228,10 @@ const ConnectDatabase: React.FC<ConnectDatabaseProps> = props => {
|
||||
title: string;
|
||||
disableOnEdit: boolean;
|
||||
}) => (
|
||||
<label className={styles.connect_db_radio_label}>
|
||||
<label
|
||||
key={`label-${radioBtn.title}`}
|
||||
className={styles.connect_db_radio_label}
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
value={radioBtn.value}
|
||||
@ -250,7 +258,10 @@ const ConnectDatabase: React.FC<ConnectDatabaseProps> = props => {
|
||||
label="Database Display Name"
|
||||
placeholder="database name"
|
||||
/>
|
||||
<label className={styles.connect_db_input_label}>
|
||||
<label
|
||||
key="Data Source Driver"
|
||||
className={styles.connect_db_input_label}
|
||||
>
|
||||
Data Source Driver
|
||||
</label>
|
||||
<select
|
||||
@ -290,7 +301,7 @@ const ConnectDatabase: React.FC<ConnectDatabaseProps> = props => {
|
||||
{connectionType === connectionTypes.ENV_VAR ? (
|
||||
<LabeledInput
|
||||
label="Environment Variable"
|
||||
placeholder="DB_URL_FROM_ENV"
|
||||
placeholder="HASURA_GRAPHQL_DB_URL_FROM_ENV"
|
||||
onChange={e =>
|
||||
connectDBDispatch({
|
||||
type: 'UPDATE_DB_URL_ENV_VAR',
|
||||
@ -448,9 +459,9 @@ const ConnectDatabase: React.FC<ConnectDatabaseProps> = props => {
|
||||
</div>
|
||||
<div className={styles.add_button_layout}>
|
||||
<Button
|
||||
onClick={onClickConnectDatabase}
|
||||
size="large"
|
||||
color="yellow"
|
||||
type="submit"
|
||||
style={{
|
||||
width: '70%',
|
||||
...(loading && { cursor: 'progress' }),
|
||||
@ -461,7 +472,7 @@ const ConnectDatabase: React.FC<ConnectDatabaseProps> = props => {
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</Tabbed>
|
||||
);
|
||||
};
|
||||
|
@ -17,7 +17,7 @@ const TabbedDSConnection: React.FC<{ tabName: 'create' | 'connect' }> = ({
|
||||
url: appPrefix,
|
||||
},
|
||||
{
|
||||
title: 'Manage Databases',
|
||||
title: 'Data Manager',
|
||||
url: `${appPrefix}/manage`,
|
||||
},
|
||||
{
|
||||
@ -35,7 +35,7 @@ const TabbedDSConnection: React.FC<{ tabName: 'create' | 'connect' }> = ({
|
||||
<CommonTabLayout
|
||||
appPrefix={appPrefix}
|
||||
currentTab={tabName}
|
||||
heading="Create database"
|
||||
heading="Connect Database"
|
||||
tabsInfo={tabs}
|
||||
breadCrumbs={breadCrumbs}
|
||||
baseUrl={`${appPrefix}/manage`}
|
||||
|
@ -5,7 +5,7 @@ import styles from './DataSources.scss';
|
||||
|
||||
const tabs: Tabs = {
|
||||
connect: {
|
||||
display_text: 'Connect existing database',
|
||||
display_text: 'Connect Existing Database',
|
||||
},
|
||||
};
|
||||
if (Globals.hasuraCloudTenantId && Globals.herokuOAuthClientId) {
|
||||
|
@ -29,6 +29,7 @@ const DataSubSidebar = props => {
|
||||
currentSchema,
|
||||
enums,
|
||||
inconsistentObjects,
|
||||
pathname,
|
||||
} = props;
|
||||
|
||||
const getItems = (schemaInfo = null) => {
|
||||
@ -146,7 +147,14 @@ const DataSubSidebar = props => {
|
||||
|
||||
useEffect(() => {
|
||||
updateTreeViewItemsWithSchemaInfo();
|
||||
}, [sources.length, tables, functions, enums, schemaList]);
|
||||
}, [
|
||||
sources.length,
|
||||
tables,
|
||||
functions,
|
||||
enums,
|
||||
schemaList,
|
||||
inconsistentObjects,
|
||||
]);
|
||||
|
||||
const databasesCount = treeViewItems?.length || 0;
|
||||
|
||||
@ -165,6 +173,7 @@ const DataSubSidebar = props => {
|
||||
onSchemaChange={onSchemaChange}
|
||||
currentDataSource={currentDataSource}
|
||||
currentSchema={currentSchema}
|
||||
pathname={pathname}
|
||||
/>
|
||||
</LeftSubSidebar>
|
||||
);
|
||||
@ -187,6 +196,7 @@ const mapStateToProps = state => {
|
||||
currentDataSource: state.tables.currentDataSource,
|
||||
currentSchema: state.tables.currentSchema,
|
||||
schemaList: state.tables.schemaList,
|
||||
pathname: state?.routing?.locationBeforeTransitions?.pathname,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -1,9 +1,9 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import Helmet from 'react-helmet';
|
||||
import { connect, ConnectedProps } from 'react-redux';
|
||||
|
||||
import Button from '../../../Common/Button/Button';
|
||||
import styles from '../../../Common/Common.scss';
|
||||
import styles from './styles.scss';
|
||||
import { ReduxState } from '../../../../types';
|
||||
import BreadCrumb from '../../../Common/Layout/BreadCrumb/BreadCrumb';
|
||||
import { DataSource } from '../../../../metadata/types';
|
||||
@ -33,10 +33,12 @@ type DatabaseListItemProps = {
|
||||
// onEdit: (dbName: string) => void;
|
||||
onReload: (name: string, driver: Driver, cb: () => void) => void;
|
||||
onRemove: (name: string, driver: Driver, cb: () => void) => void;
|
||||
pushRoute: (route: string) => void;
|
||||
};
|
||||
|
||||
const DatabaseListItem: React.FC<DatabaseListItemProps> = ({
|
||||
// onEdit,
|
||||
pushRoute,
|
||||
onReload,
|
||||
onRemove,
|
||||
dataSource,
|
||||
@ -46,18 +48,27 @@ const DatabaseListItem: React.FC<DatabaseListItemProps> = ({
|
||||
const [removing, setRemoving] = useState(false);
|
||||
const [showUrl, setShowUrl] = useState(false);
|
||||
|
||||
const viewDB = () => {
|
||||
if (dataSource?.name) pushRoute(`/data/${dataSource.name}`);
|
||||
};
|
||||
const isInconsistentDataSource = isInconsistentSource(
|
||||
dataSource.name,
|
||||
inconsistentObjects
|
||||
);
|
||||
return (
|
||||
<div className={styles.db_list_item}>
|
||||
<div className={styles.db_item_actions}>
|
||||
{/* Edit shall be cut out until we have a server API
|
||||
<div
|
||||
className={`${styles.flex_space_between} ${styles.add_pad_min} ${styles.db_list_item}`}
|
||||
>
|
||||
<div className={styles.display_flex}>
|
||||
<Button
|
||||
size="xs"
|
||||
color="white"
|
||||
style={{ marginRight: '10px' }}
|
||||
onClick={() => onEdit(dataSource.name)}
|
||||
onClick={viewDB}
|
||||
disabled={isInconsistentDataSource}
|
||||
>
|
||||
Edit
|
||||
</Button> */}
|
||||
View Database
|
||||
</Button>
|
||||
<Button
|
||||
size="xs"
|
||||
color="white"
|
||||
@ -84,32 +95,31 @@ const DatabaseListItem: React.FC<DatabaseListItemProps> = ({
|
||||
>
|
||||
{removing ? 'Removing...' : 'Remove'}
|
||||
</Button>
|
||||
</div>
|
||||
<div className={styles.db_list_content}>
|
||||
<div className={styles.db_display_data}>
|
||||
<div className={styles.displayFlexContainer}>
|
||||
<b>{dataSource.name}</b>
|
||||
<p>({driverToLabel[dataSource.driver]})</p>
|
||||
</div>
|
||||
<p style={{ marginTop: -5 }}>
|
||||
{getHostFromConnectionString(dataSource)}
|
||||
</p>
|
||||
<div
|
||||
className={`${styles.displayFlexContainer} ${styles.add_pad_left} ${styles.add_pad_top_10}`}
|
||||
>
|
||||
<b>{dataSource.name}</b>
|
||||
<p>({driverToLabel[dataSource.driver]})</p>
|
||||
</div>
|
||||
{isInconsistentSource(dataSource.name, inconsistentObjects) && (
|
||||
<p className={`${styles.add_pad_top_10} ${styles.add_pad_left}`}>
|
||||
{getHostFromConnectionString(dataSource)}
|
||||
</p>
|
||||
{isInconsistentDataSource && (
|
||||
<ToolTip
|
||||
id={`inconsistent-source-${dataSource.name}`}
|
||||
placement="right"
|
||||
message="Inconsistent Data Source"
|
||||
>
|
||||
<i
|
||||
className="fa fa-exclamation-triangle"
|
||||
className={`fa fa-exclamation-triangle ${styles.inconsistentSourceIcon}`}
|
||||
aria-hidden="true"
|
||||
style={{ padding: 3, color: '#c02020' }}
|
||||
/>
|
||||
</ToolTip>
|
||||
)}
|
||||
</div>
|
||||
<span style={{ paddingLeft: 125 }}>
|
||||
<span
|
||||
className={`${styles.db_large_string_break_words} ${styles.add_pad_top_10}`}
|
||||
>
|
||||
{showUrl ? (
|
||||
typeof dataSource.url === 'string' ? (
|
||||
dataSource.url
|
||||
@ -149,11 +159,20 @@ const DatabaseListItem: React.FC<DatabaseListItemProps> = ({
|
||||
|
||||
interface ManageDatabaseProps extends InjectedProps {}
|
||||
|
||||
let autoRedirectedToConnectPage = false;
|
||||
|
||||
const ManageDatabase: React.FC<ManageDatabaseProps> = ({
|
||||
dataSources,
|
||||
dispatch,
|
||||
inconsistentObjects,
|
||||
location,
|
||||
}) => {
|
||||
useEffect(() => {
|
||||
if (dataSources.length === 0 && !autoRedirectedToConnectPage) {
|
||||
dispatch(_push('/data/manage/connect'));
|
||||
autoRedirectedToConnectPage = true;
|
||||
}
|
||||
}, [location, dataSources, dispatch]);
|
||||
const crumbs = [
|
||||
{
|
||||
title: 'Data',
|
||||
@ -192,6 +211,10 @@ const ManageDatabase: React.FC<ManageDatabaseProps> = ({
|
||||
dispatch(_push('/data/manage/connect'));
|
||||
};
|
||||
|
||||
const pushRoute = (route: string) => {
|
||||
if (route) dispatch(_push(route));
|
||||
};
|
||||
|
||||
// const onEdit = (dbName: string) => {
|
||||
// dispatch(_push(`/data/manage/edit/${dbName}`));
|
||||
// };
|
||||
@ -206,7 +229,7 @@ const ManageDatabase: React.FC<ManageDatabaseProps> = ({
|
||||
<h2
|
||||
className={`${styles.headerText} ${styles.display_inline} ${styles.add_mar_right}`}
|
||||
>
|
||||
Manage Databases
|
||||
Data Manager
|
||||
</h2>
|
||||
<Button
|
||||
color="yellow"
|
||||
@ -220,17 +243,15 @@ const ManageDatabase: React.FC<ManageDatabaseProps> = ({
|
||||
</div>
|
||||
<div className={styles.manage_db_content}>
|
||||
<hr />
|
||||
<h3 className={`${styles.heading_text} ${styles.remove_pad_bottom}`}>
|
||||
Connected Databases
|
||||
</h3>
|
||||
<div className={styles.data_list_container}>
|
||||
<h3 className={styles.heading_text}>Connected Databases</h3>
|
||||
<div className={styles.flexColumn}>
|
||||
{dataSources.length ? (
|
||||
dataSources.map(data => (
|
||||
<DatabaseListItem
|
||||
key={data.name}
|
||||
dataSource={data}
|
||||
inconsistentObjects={inconsistentObjects}
|
||||
// onEdit={onEdit}
|
||||
pushRoute={pushRoute}
|
||||
onReload={onReload}
|
||||
onRemove={onRemove}
|
||||
/>
|
||||
@ -255,6 +276,7 @@ const mapStateToProps = (state: ReduxState) => {
|
||||
currentDataSource: state.tables.currentDataSource,
|
||||
currentSchema: state.tables.currentSchema,
|
||||
inconsistentObjects: state.metadata.inconsistentObjects,
|
||||
location: state?.routing?.locationBeforeTransitions,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -44,6 +44,21 @@ import { RightContainer } from '../../../Common/Layout/RightContainer';
|
||||
import { TrackableFunctionsList } from './FunctionsList';
|
||||
import { getTrackableFunctions } from './utils';
|
||||
|
||||
const FEATURES = {
|
||||
UNTRACKED_TABLES: 'UNTRACKED_TABLES',
|
||||
UNTRACKED_RELATIONS: 'UNTRACKED_RELATIONS',
|
||||
UNTRACKED_FUNCTION: 'UNTRACKED_FUNCTION',
|
||||
NON_TRACKABLE_FUNCTIONS: 'NON_TRACKABLE_FUNCTIONS',
|
||||
};
|
||||
|
||||
const supportedFeaturesByDriver = {
|
||||
postgres: Object.values(FEATURES),
|
||||
mssql: [FEATURES.UNTRACKED_TABLES, FEATURES.UNTRACKED_RELATIONS],
|
||||
};
|
||||
|
||||
const isAllowed = (driver, feature) =>
|
||||
supportedFeaturesByDriver[driver].includes(feature);
|
||||
|
||||
const DeleteSchemaButton = ({ dispatch, migrationMode, currentDataSource }) => {
|
||||
const successCb = () => {
|
||||
dispatch(updateCurrentSchema('public', currentDataSource));
|
||||
@ -540,7 +555,7 @@ class Schema extends Component {
|
||||
);
|
||||
};
|
||||
|
||||
const getUntrackedFunctionsSection = () => {
|
||||
const getUntrackedFunctionsSection = isSupported => {
|
||||
const heading = getSectionHeading(
|
||||
'Untracked custom functions',
|
||||
'Custom functions that are not exposed over the GraphQL API',
|
||||
@ -555,12 +570,18 @@ class Schema extends Component {
|
||||
testId={'toggle-trackable-functions'}
|
||||
>
|
||||
<div className={`${styles.padd_left_remove} col-xs-12`}>
|
||||
<TrackableFunctionsList
|
||||
dispatch={dispatch}
|
||||
funcs={trackableFuncs}
|
||||
readOnlyMode={readOnlyMode}
|
||||
source={currentDataSource}
|
||||
/>
|
||||
{isSupported ? (
|
||||
<TrackableFunctionsList
|
||||
dispatch={dispatch}
|
||||
funcs={trackableFuncs}
|
||||
readOnlyMode={readOnlyMode}
|
||||
source={currentDataSource}
|
||||
/>
|
||||
) : (
|
||||
`Currently unsupported for ${(
|
||||
currentDriver + ''
|
||||
).toUpperCase()}`
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.clear_fix} />
|
||||
</CollapsibleToggle>
|
||||
@ -646,8 +667,11 @@ class Schema extends Component {
|
||||
<hr />
|
||||
{getUntrackedTablesSection()}
|
||||
{getUntrackedRelationsSection()}
|
||||
{getUntrackedFunctionsSection()}
|
||||
{getNonTrackableFunctionsSection()}
|
||||
{getUntrackedFunctionsSection(
|
||||
isAllowed(currentDriver, FEATURES.NON_TRACKABLE_FUNCTIONS)
|
||||
)}
|
||||
{isAllowed(currentDriver, FEATURES.NON_TRACKABLE_FUNCTIONS) &&
|
||||
getNonTrackableFunctionsSection()}
|
||||
<hr />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -46,3 +46,9 @@
|
||||
width: 420px;
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.inconsistentSourceIcon {
|
||||
padding: 3px;
|
||||
color: #c02020;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
@ -29,17 +29,18 @@ type LeafItemsViewProps = {
|
||||
item: SourceItem;
|
||||
currentSource: string;
|
||||
currentSchema: string;
|
||||
setActiveTable: (value: string) => void;
|
||||
isActive: boolean;
|
||||
pathname: string;
|
||||
};
|
||||
const LeafItemsView: React.FC<LeafItemsViewProps> = ({
|
||||
item,
|
||||
currentSource,
|
||||
currentSchema,
|
||||
setActiveTable,
|
||||
isActive,
|
||||
pathname,
|
||||
}) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const isActive = pathname.includes(
|
||||
`/data/${currentSource}/schema/${currentSchema}/tables/${item.name}`
|
||||
);
|
||||
|
||||
const isView = item.type === 'view';
|
||||
|
||||
@ -59,11 +60,9 @@ const LeafItemsView: React.FC<LeafItemsViewProps> = ({
|
||||
<div
|
||||
onClick={() => {
|
||||
setIsOpen(prev => !prev);
|
||||
setActiveTable(item.name);
|
||||
}}
|
||||
onKeyDown={() => {
|
||||
setIsOpen(prev => !prev);
|
||||
setActiveTable(item.name);
|
||||
}}
|
||||
role="button"
|
||||
>
|
||||
@ -126,27 +125,21 @@ type SchemaItemsViewProps = {
|
||||
currentSource: string;
|
||||
isActive: boolean;
|
||||
setActiveSchema: (value: string) => void;
|
||||
pathname: string;
|
||||
};
|
||||
const SchemaItemsView: React.FC<SchemaItemsViewProps> = ({
|
||||
item,
|
||||
currentSource,
|
||||
isActive,
|
||||
setActiveSchema,
|
||||
pathname,
|
||||
}) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [activeTable, setActiveTable] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isActive) {
|
||||
setActiveTable(null);
|
||||
}
|
||||
setIsOpen(isActive);
|
||||
}, [isActive]);
|
||||
|
||||
const handleActiveTable = (value: string) => {
|
||||
setActiveTable(value);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
@ -178,9 +171,8 @@ const SchemaItemsView: React.FC<SchemaItemsViewProps> = ({
|
||||
item={child}
|
||||
currentSource={currentSource}
|
||||
currentSchema={item.name}
|
||||
setActiveTable={handleActiveTable}
|
||||
isActive={activeTable === child.name}
|
||||
key={key}
|
||||
pathname={pathname}
|
||||
/>
|
||||
</li>
|
||||
))
|
||||
@ -196,6 +188,7 @@ type DatabaseItemsViewProps = {
|
||||
setActiveDataSource: (activeSource: string) => void;
|
||||
onSchemaChange: (value: string) => void;
|
||||
currentSchema: string;
|
||||
pathname: string;
|
||||
};
|
||||
const DatabaseItemsView: React.FC<DatabaseItemsViewProps> = ({
|
||||
item,
|
||||
@ -203,6 +196,7 @@ const DatabaseItemsView: React.FC<DatabaseItemsViewProps> = ({
|
||||
setActiveDataSource,
|
||||
onSchemaChange,
|
||||
currentSchema,
|
||||
pathname,
|
||||
}) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
@ -246,6 +240,7 @@ const DatabaseItemsView: React.FC<DatabaseItemsViewProps> = ({
|
||||
isActive={child.name === currentSchema}
|
||||
setActiveSchema={handleSelectSchema}
|
||||
key={key}
|
||||
pathname={pathname}
|
||||
/>
|
||||
</li>
|
||||
))
|
||||
@ -260,6 +255,7 @@ type TreeViewProps = {
|
||||
onSchemaChange: (value: string) => void;
|
||||
currentDataSource: string;
|
||||
currentSchema: string;
|
||||
pathname: string;
|
||||
};
|
||||
const TreeView: React.FC<TreeViewProps> = ({
|
||||
items,
|
||||
@ -267,6 +263,7 @@ const TreeView: React.FC<TreeViewProps> = ({
|
||||
currentDataSource,
|
||||
onSchemaChange,
|
||||
currentSchema,
|
||||
pathname,
|
||||
}) => {
|
||||
const handleSelectDataSource = (dataSource: string) => {
|
||||
onDatabaseChange(dataSource);
|
||||
@ -290,6 +287,7 @@ const TreeView: React.FC<TreeViewProps> = ({
|
||||
isActive={currentDataSource === item.name}
|
||||
setActiveDataSource={handleSelectDataSource}
|
||||
currentSchema={currentSchema}
|
||||
pathname={pathname}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
@ -4,6 +4,7 @@ import { HasuraMetadataV2, HasuraMetadataV3, RestEndpointEntry } from './types';
|
||||
import {
|
||||
showSuccessNotification,
|
||||
showErrorNotification,
|
||||
showNotification,
|
||||
} from '../components/Services/Common/Notification';
|
||||
import {
|
||||
deleteAllowListQuery,
|
||||
@ -254,18 +255,35 @@ export const addDataSource = (
|
||||
|
||||
return dispatch(requestAction(Endpoints.metadata, options))
|
||||
.then(() => {
|
||||
successCb();
|
||||
if (!skipNotification) {
|
||||
dispatch(showSuccessNotification('Data source added successfully!'));
|
||||
}
|
||||
dispatch(exportMetadata());
|
||||
dispatch({
|
||||
type: UPDATE_CURRENT_DATA_SOURCE,
|
||||
source: data.payload.name,
|
||||
});
|
||||
setDriver(data.driver);
|
||||
dispatch(fetchDataInit(data.payload.name, data.driver));
|
||||
return getState();
|
||||
const onButtonClick = () => {
|
||||
if (data.payload.name) dispatch(_push(`/data/${data.payload.name}`));
|
||||
};
|
||||
return dispatch(exportMetadata()).then(() => {
|
||||
dispatch(fetchDataInit(data.payload.name, data.driver));
|
||||
if (!skipNotification) {
|
||||
dispatch(
|
||||
showNotification(
|
||||
{
|
||||
title: 'Database added successfully!',
|
||||
level: 'success',
|
||||
autoDismiss: 0,
|
||||
action: {
|
||||
label: 'View Database',
|
||||
callback: onButtonClick,
|
||||
},
|
||||
},
|
||||
'success'
|
||||
)
|
||||
);
|
||||
}
|
||||
successCb();
|
||||
return getState();
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
@ -366,34 +384,6 @@ export const editDataSource = (
|
||||
});
|
||||
};
|
||||
|
||||
export const reloadDataSource = (
|
||||
data: ReloadDataSourceRequest['data']
|
||||
): Thunk<Promise<void | ReduxState>, MetadataActions> => (
|
||||
dispatch,
|
||||
getState
|
||||
) => {
|
||||
const { dataHeaders } = getState().tables;
|
||||
|
||||
const query = reloadSource(data.name);
|
||||
|
||||
const options = {
|
||||
method: 'POST',
|
||||
headers: dataHeaders,
|
||||
body: JSON.stringify(query),
|
||||
};
|
||||
|
||||
return dispatch(requestAction(Endpoints.metadata, options))
|
||||
.then(() => {
|
||||
dispatch(showSuccessNotification('Data source reloaded successfully!'));
|
||||
dispatch(exportMetadata());
|
||||
return getState();
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
dispatch(showErrorNotification('Reload data source failed', null, err));
|
||||
});
|
||||
};
|
||||
|
||||
export const replaceMetadata = (
|
||||
newMetadata: HasuraMetadataV2,
|
||||
successCb: () => void,
|
||||
@ -461,6 +451,7 @@ export const resetMetadata = (
|
||||
requestAction(Endpoints.metadata, options as RequestInit)
|
||||
).then(
|
||||
() => {
|
||||
dispatch({ type: UPDATE_CURRENT_DATA_SOURCE, source: '' });
|
||||
dispatch(exportMetadata());
|
||||
if (successCb) {
|
||||
successCb();
|
||||
@ -584,7 +575,34 @@ export const loadInconsistentObjects = (
|
||||
);
|
||||
};
|
||||
};
|
||||
export const reloadDataSource = (
|
||||
data: ReloadDataSourceRequest['data']
|
||||
): Thunk<Promise<void | ReduxState>, MetadataActions> => (
|
||||
dispatch,
|
||||
getState
|
||||
) => {
|
||||
const { dataHeaders } = getState().tables;
|
||||
|
||||
const query = reloadSource(data.name);
|
||||
|
||||
const options = {
|
||||
method: 'POST',
|
||||
headers: dataHeaders,
|
||||
body: JSON.stringify(query),
|
||||
};
|
||||
|
||||
return dispatch(requestAction(Endpoints.metadata, options))
|
||||
.then(() => {
|
||||
dispatch(showSuccessNotification('Data source reloaded successfully!'));
|
||||
dispatch(exportMetadata());
|
||||
dispatch(loadInconsistentObjects({ shouldReloadMetadata: false }));
|
||||
return getState();
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
dispatch(showErrorNotification('Reload data source failed', null, err));
|
||||
});
|
||||
};
|
||||
export const reloadRemoteSchema = (
|
||||
remoteSchemaName: string,
|
||||
successCb: () => void,
|
||||
|
Loading…
Reference in New Issue
Block a user