mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-15 01:12:56 +03:00
console: allow editing sources configuration
This adds the feature to edit data sources to the console Co-authored-by: Aleksandra Sikora <9019397+beerose@users.noreply.github.com> Co-authored-by: Rakesh Emmadi <12475069+rakeshkky@users.noreply.github.com> Co-authored-by: Rikin Kachhia <54616969+rikinsk@users.noreply.github.com> Co-authored-by: Martin Mark <74692114+martin-hasura@users.noreply.github.com> GitOrigin-RevId: 40f97a362620e9cebe97a2267cb9fb143c32af5d
This commit is contained in:
parent
8edd3f03a5
commit
a7639145fe
@ -43,6 +43,7 @@ $ hasura metadata export -o json
|
||||
- server: fix regression: `on_conflict` was missing in the schema for inserts in tables where the current user has no columns listed in their update permissions (fix #6804)
|
||||
- server: fix one-to-one relationship bug which prevented adding one-to-one relationships which didn't have the same column name for target and source
|
||||
- console: fix Postgres table creation when table has a non-lowercase name and a comment (#6760)
|
||||
- console: allow editing sources configuration
|
||||
- cli: fix regression - `metadata apply —dry-run` was overwriting local metadata files with metadata on server when it should just display the differences.
|
||||
- server: decrease polling interval for scheduled triggers from 60 to 10 seconds
|
||||
- server: Change `HASURA_GRAPHQL_SCHEMA_POLL_INTERVAL` env var to `HASURA_GRAPHQL_SCHEMA_SYNC_POLL_INTERVAL` and `schema-poll-interval` option to `--schema-sync-poll-interval`.
|
||||
|
@ -39,7 +39,7 @@ export const addsNewPostgresDatabaseWithUrl = () => {
|
||||
cy.getBySel('connect-database-btn').click();
|
||||
cy.get('.notification-success', { timeout: 10000 })
|
||||
.should('be.visible')
|
||||
.and('contain', 'Database added successfully!');
|
||||
.and('contain', 'Data source added successfully!');
|
||||
cy.url().should('eq', `${baseUrl}/data/manage`);
|
||||
};
|
||||
|
||||
@ -59,7 +59,7 @@ export const addsNewPgDBWithConParams = () => {
|
||||
cy.getBySel('connect-database-btn').click();
|
||||
cy.get('.notification-success', { timeout: 10000 })
|
||||
.should('be.visible')
|
||||
.and('contain', 'Database added successfully!');
|
||||
.and('contain', 'Data source added successfully!');
|
||||
cy.url().should('eq', `${baseUrl}/data/manage`);
|
||||
};
|
||||
|
||||
@ -73,7 +73,7 @@ export const addsNewPgDBWithEnvVar = () => {
|
||||
cy.getBySel('connect-database-btn').click();
|
||||
cy.get('.notification-success', { timeout: 10000 })
|
||||
.should('be.visible')
|
||||
.and('contain', 'Database added successfully!');
|
||||
.and('contain', 'Data source added successfully!');
|
||||
cy.url().should('eq', `${baseUrl}/data/manage`);
|
||||
};
|
||||
|
||||
|
@ -547,6 +547,10 @@ input {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.add_pad_left_mid {
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.add_mar_small {
|
||||
margin-right: 10px !important;
|
||||
}
|
||||
@ -1512,6 +1516,10 @@ code {
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
.label_disabled {
|
||||
color: #4d4d4db3;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.connect_db_radio_label {
|
||||
margin-right: 24px;
|
||||
@ -1560,7 +1568,7 @@ code {
|
||||
flex-direction: row;
|
||||
margin-top: 12px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.connection_settings_form_input_layout {
|
||||
|
@ -4,37 +4,38 @@ import { ConnectDBActions, ConnectDBState, connectionTypes } from './state';
|
||||
import { LabeledInput } from '../../../Common/LabeledInput';
|
||||
import Tooltip from '../../../Common/Tooltip/Tooltip';
|
||||
import { Driver, getSupportedDrivers } from '../../../../dataSources';
|
||||
import { readFile } from './utils';
|
||||
|
||||
import styles from './DataSources.scss';
|
||||
import JSONEditor from '../TablePermissions/JSONEditor';
|
||||
import { SupportedFeaturesType } from '../../../../dataSources/types';
|
||||
import { Path } from '../../../Common/utils/tsUtils';
|
||||
|
||||
type ConnectDatabaseFormProps = {
|
||||
export interface ConnectDatabaseFormProps {
|
||||
// Connect DB State Props
|
||||
connectionDBState: ConnectDBState;
|
||||
connectionDBStateDispatch: Dispatch<ConnectDBActions>;
|
||||
// Connection Type Props - for the Radio buttons
|
||||
updateConnectionTypeRadio: (e: ChangeEvent<HTMLInputElement>) => void;
|
||||
changeConnectionType?: (value: string) => void;
|
||||
connectionTypeState: string;
|
||||
// Other Props
|
||||
isreadreplica?: boolean;
|
||||
isEditState?: boolean;
|
||||
title?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export const connectionRadios = [
|
||||
{
|
||||
value: connectionTypes.CONNECTION_PARAMS,
|
||||
title: 'Connection Parameters',
|
||||
disableOnEdit: true,
|
||||
},
|
||||
{
|
||||
value: connectionTypes.DATABASE_URL,
|
||||
title: 'Database URL',
|
||||
disableOnEdit: false,
|
||||
},
|
||||
{
|
||||
value: connectionTypes.ENV_VAR,
|
||||
title: 'Environment Variable',
|
||||
disableOnEdit: true,
|
||||
},
|
||||
];
|
||||
|
||||
@ -48,11 +49,24 @@ const dbTypePlaceholders: Record<Driver, string> = {
|
||||
|
||||
const defaultTitle = 'Connect Database Via';
|
||||
|
||||
const driverToLabel: Record<Driver, string> = {
|
||||
mysql: 'MySQL',
|
||||
postgres: 'PostgreSQL',
|
||||
mssql: 'MS Server',
|
||||
bigquery: 'BigQuery',
|
||||
const driverToLabel: Record<
|
||||
Driver,
|
||||
{ label: string; defaultConnection: string; info?: string }
|
||||
> = {
|
||||
mysql: { label: 'MySQL', defaultConnection: 'DATABASE_URL' },
|
||||
postgres: { label: 'PostgreSQL', defaultConnection: 'DATABASE_URL' },
|
||||
mssql: {
|
||||
label: 'MS Server',
|
||||
defaultConnection: 'DATABASE_URL',
|
||||
info:
|
||||
'Only Database URLs and Environment Variables are available using MSSQL',
|
||||
},
|
||||
bigquery: {
|
||||
label: 'BigQuery',
|
||||
defaultConnection: 'CONNECTION_PARAMETERS',
|
||||
info:
|
||||
'Only Connection Parameters and Environment Variables are available using BigQuery',
|
||||
},
|
||||
};
|
||||
|
||||
const supportedDrivers = getSupportedDrivers('connectDbForm.enabled');
|
||||
@ -60,9 +74,11 @@ const supportedDrivers = getSupportedDrivers('connectDbForm.enabled');
|
||||
const ConnectDatabaseForm: React.FC<ConnectDatabaseFormProps> = ({
|
||||
connectionDBState,
|
||||
connectionDBStateDispatch,
|
||||
changeConnectionType,
|
||||
updateConnectionTypeRadio,
|
||||
connectionTypeState,
|
||||
isreadreplica = false,
|
||||
isEditState = false,
|
||||
title,
|
||||
}) => {
|
||||
const [currentConnectionParamState, toggleConnectionParamState] = useState(
|
||||
@ -72,20 +88,29 @@ const ConnectDatabaseForm: React.FC<ConnectDatabaseFormProps> = ({
|
||||
toggleConnectionParamState(value);
|
||||
};
|
||||
|
||||
const handleFileUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files![0];
|
||||
const addFileQueries = (content: string) => {
|
||||
try {
|
||||
connectionDBStateDispatch({
|
||||
type: 'UPDATE_DB_BIGQUERY_SERVICE_ACCOUNT_FILE',
|
||||
data: content,
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
const isDBSupported = (driver: Driver, connectionType: string) => {
|
||||
let ts = 'databaseURL';
|
||||
if (connectionType === 'CONNECTION_PARAMETERS') {
|
||||
ts = 'connectionParameters';
|
||||
}
|
||||
if (connectionType === 'ENVIRONMENT_VARIABLES') {
|
||||
ts = 'environmentVariable';
|
||||
}
|
||||
return getSupportedDrivers(
|
||||
`connectDbForm.${ts}` as Path<SupportedFeaturesType>
|
||||
).includes(driver);
|
||||
};
|
||||
|
||||
readFile(file, addFileQueries);
|
||||
const handleDBChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
const value = e.target.value as Driver;
|
||||
const isSupported = isDBSupported(value, connectionTypeState);
|
||||
connectionDBStateDispatch({
|
||||
type: 'UPDATE_DB_DRIVER',
|
||||
data: value,
|
||||
});
|
||||
if (!isSupported && changeConnectionType) {
|
||||
changeConnectionType(driverToLabel[value].defaultConnection);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
@ -100,7 +125,11 @@ const ConnectDatabaseForm: React.FC<ConnectDatabaseFormProps> = ({
|
||||
{connectionRadios.map(radioBtn => (
|
||||
<label
|
||||
key={`label-${radioBtn.title}`}
|
||||
className={styles.connect_db_radio_label}
|
||||
className={`${styles.connect_db_radio_label} ${
|
||||
!isDBSupported(connectionDBState.dbType, radioBtn.value)
|
||||
? styles.label_disabled
|
||||
: ''
|
||||
}`}
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
@ -114,14 +143,20 @@ const ConnectDatabaseForm: React.FC<ConnectDatabaseFormProps> = ({
|
||||
defaultChecked={
|
||||
connectionTypeState === connectionTypes.DATABASE_URL
|
||||
}
|
||||
// disabled={
|
||||
// isEditState === radioBtn.disableOnEdit && isEditState
|
||||
// }
|
||||
disabled={
|
||||
!isDBSupported(connectionDBState.dbType, radioBtn.value)
|
||||
}
|
||||
/>
|
||||
{radioBtn.title}
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
{driverToLabel[connectionDBState.dbType].info && (
|
||||
<div className={styles.info_label}>
|
||||
<i className="fa fa-info-circle" aria-hidden="true" />
|
||||
<span>{driverToLabel[connectionDBState.dbType].info}</span>
|
||||
</div>
|
||||
)}
|
||||
<div className={styles.connect_form_layout}>
|
||||
{!isreadreplica && (
|
||||
<>
|
||||
@ -132,6 +167,7 @@ const ConnectDatabaseForm: React.FC<ConnectDatabaseFormProps> = ({
|
||||
data: e.target.value,
|
||||
})
|
||||
}
|
||||
disabled={isEditState}
|
||||
value={connectionDBState.displayName}
|
||||
label="Database Display Name"
|
||||
placeholder="database name"
|
||||
@ -146,27 +182,22 @@ const ConnectDatabaseForm: React.FC<ConnectDatabaseFormProps> = ({
|
||||
<select
|
||||
key="connect-db-type"
|
||||
value={connectionDBState.dbType}
|
||||
onChange={e =>
|
||||
connectionDBStateDispatch({
|
||||
type: 'UPDATE_DB_DRIVER',
|
||||
data: e.target.value as Driver,
|
||||
})
|
||||
}
|
||||
onChange={handleDBChange}
|
||||
className={`form-control ${styles.connect_db_input_pad}`}
|
||||
disabled={isEditState}
|
||||
data-test="database-type"
|
||||
>
|
||||
{supportedDrivers.map(driver => (
|
||||
<option key={driver} value={driver}>
|
||||
{driverToLabel[driver]}
|
||||
{driverToLabel[driver].label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</>
|
||||
)}
|
||||
{(connectionTypeState.includes(connectionTypes.DATABASE_URL) ||
|
||||
(connectionTypeState.includes(connectionTypes.CONNECTION_PARAMS) &&
|
||||
connectionDBState.dbType === 'mssql')) &&
|
||||
connectionDBState.dbType !== 'bigquery' ? (
|
||||
{connectionTypeState.includes(connectionTypes.DATABASE_URL) ||
|
||||
(connectionTypeState.includes(connectionTypes.CONNECTION_PARAMS) &&
|
||||
connectionDBState.dbType === 'mssql') ? (
|
||||
<LabeledInput
|
||||
label="Database URL"
|
||||
onChange={e =>
|
||||
@ -178,7 +209,6 @@ const ConnectDatabaseForm: React.FC<ConnectDatabaseFormProps> = ({
|
||||
value={connectionDBState.databaseURLState.dbURL}
|
||||
placeholder={dbTypePlaceholders[connectionDBState.dbType]}
|
||||
data-test="database-url"
|
||||
// disabled={isEditState}
|
||||
/>
|
||||
) : null}
|
||||
{connectionTypeState.includes(connectionTypes.ENV_VAR) &&
|
||||
@ -217,13 +247,19 @@ const ConnectDatabaseForm: React.FC<ConnectDatabaseFormProps> = ({
|
||||
) : (
|
||||
<div className={styles.add_mar_bottom_mid}>
|
||||
<div className={styles.add_mar_bottom_mid}>
|
||||
<b>Service Account File:</b>
|
||||
<Tooltip message="Service account key file for bigquery db" />
|
||||
<b>Service Account Key:</b>
|
||||
<Tooltip message="Service account key for BigQuery data source" />
|
||||
</div>
|
||||
<input
|
||||
type="file"
|
||||
className={`form-control input-sm ${styles.inline_block}`}
|
||||
onChange={handleFileUpload}
|
||||
<JSONEditor
|
||||
minLines={5}
|
||||
initData="{}"
|
||||
onChange={value => {
|
||||
connectionDBStateDispatch({
|
||||
type: 'UPDATE_DB_BIGQUERY_SERVICE_ACCOUNT',
|
||||
data: value,
|
||||
});
|
||||
}}
|
||||
data={connectionDBState.databaseURLState.serviceAccount}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
@ -254,156 +290,168 @@ const ConnectDatabaseForm: React.FC<ConnectDatabaseFormProps> = ({
|
||||
</>
|
||||
) : null}
|
||||
{connectionTypeState.includes(connectionTypes.CONNECTION_PARAMS) &&
|
||||
connectionDBState.dbType === 'postgres' ? (
|
||||
<>
|
||||
<LabeledInput
|
||||
label="Host"
|
||||
placeholder="localhost"
|
||||
onChange={e =>
|
||||
connectionDBStateDispatch({
|
||||
type: 'UPDATE_DB_HOST',
|
||||
data: e.target.value,
|
||||
})
|
||||
}
|
||||
value={connectionDBState.connectionParamState.host}
|
||||
data-test="host"
|
||||
/>
|
||||
<LabeledInput
|
||||
label="Port"
|
||||
placeholder="5432"
|
||||
onChange={e =>
|
||||
connectionDBStateDispatch({
|
||||
type: 'UPDATE_DB_PORT',
|
||||
data: e.target.value,
|
||||
})
|
||||
}
|
||||
value={connectionDBState.connectionParamState.port}
|
||||
data-test="port"
|
||||
/>
|
||||
<LabeledInput
|
||||
label="Username"
|
||||
placeholder="postgres_user"
|
||||
onChange={e =>
|
||||
connectionDBStateDispatch({
|
||||
type: 'UPDATE_DB_USERNAME',
|
||||
data: e.target.value,
|
||||
})
|
||||
}
|
||||
value={connectionDBState.connectionParamState.username}
|
||||
data-test="username"
|
||||
/>
|
||||
<LabeledInput
|
||||
label="Password"
|
||||
key="connect-db-password"
|
||||
type="password"
|
||||
placeholder="postgrespassword"
|
||||
onChange={e =>
|
||||
connectionDBStateDispatch({
|
||||
type: 'UPDATE_DB_PASSWORD',
|
||||
data: e.target.value,
|
||||
})
|
||||
}
|
||||
value={connectionDBState.connectionParamState.password}
|
||||
data-test="password"
|
||||
/>
|
||||
<LabeledInput
|
||||
key="connect-db-database-name"
|
||||
label="Database Name"
|
||||
placeholder="postgres"
|
||||
onChange={e =>
|
||||
connectionDBStateDispatch({
|
||||
type: 'UPDATE_DB_DATABASE_NAME',
|
||||
data: e.target.value,
|
||||
})
|
||||
}
|
||||
value={connectionDBState.connectionParamState.database}
|
||||
data-test="database-name"
|
||||
/>
|
||||
</>
|
||||
) : null}
|
||||
<div className={styles.connection_settings_layout}>
|
||||
<div className={styles.connection_settings_header}>
|
||||
<a
|
||||
href="#"
|
||||
style={{ textDecoration: 'none' }}
|
||||
onClick={toggleConnectionParams(!currentConnectionParamState)}
|
||||
>
|
||||
{currentConnectionParamState ? (
|
||||
<i className="fa fa-caret-down" />
|
||||
) : (
|
||||
<i className="fa fa-caret-right" />
|
||||
)}
|
||||
{' '}
|
||||
Connection Settings
|
||||
</a>
|
||||
</div>
|
||||
{currentConnectionParamState ? (
|
||||
<div className={styles.connection_settings_form}>
|
||||
<div className={styles.connection_settings_form_input_layout}>
|
||||
<LabeledInput
|
||||
label="Max Connections"
|
||||
type="number"
|
||||
className={`form-control ${styles.connnection_settings_form_input}`}
|
||||
placeholder="50"
|
||||
value={
|
||||
connectionDBState.connectionSettings?.max_connections ??
|
||||
undefined
|
||||
}
|
||||
onChange={e =>
|
||||
connectionDBStateDispatch({
|
||||
type: 'UPDATE_MAX_CONNECTIONS',
|
||||
data: e.target.value,
|
||||
})
|
||||
}
|
||||
min="0"
|
||||
boldlabel
|
||||
data-test="max-connections"
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.connection_settings_form_input_layout}>
|
||||
<LabeledInput
|
||||
label="Idle Timeout"
|
||||
type="number"
|
||||
className={`form-control ${styles.connnection_settings_form_input}`}
|
||||
placeholder="180"
|
||||
value={
|
||||
connectionDBState.connectionSettings?.idle_timeout ??
|
||||
undefined
|
||||
}
|
||||
onChange={e =>
|
||||
connectionDBStateDispatch({
|
||||
type: 'UPDATE_IDLE_TIMEOUT',
|
||||
data: e.target.value,
|
||||
})
|
||||
}
|
||||
min="0"
|
||||
boldlabel
|
||||
data-test="idle-timeout"
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.connection_settings_form_input_layout}>
|
||||
<LabeledInput
|
||||
label="Retries"
|
||||
type="number"
|
||||
className={`form-control ${styles.connnection_settings_form_input}`}
|
||||
placeholder="1"
|
||||
value={
|
||||
connectionDBState.connectionSettings?.retries ?? undefined
|
||||
}
|
||||
onChange={e =>
|
||||
connectionDBStateDispatch({
|
||||
type: 'UPDATE_RETRIES',
|
||||
data: e.target.value,
|
||||
})
|
||||
}
|
||||
min="0"
|
||||
boldlabel
|
||||
data-test="retries"
|
||||
/>
|
||||
</div>
|
||||
getSupportedDrivers('connectDbForm.connectionParameters').includes(
|
||||
connectionDBState.dbType
|
||||
) &&
|
||||
connectionDBState.dbType !== 'bigquery' && (
|
||||
<>
|
||||
<LabeledInput
|
||||
label="Host"
|
||||
placeholder="localhost"
|
||||
onChange={e =>
|
||||
connectionDBStateDispatch({
|
||||
type: 'UPDATE_DB_HOST',
|
||||
data: e.target.value,
|
||||
})
|
||||
}
|
||||
value={connectionDBState.connectionParamState.host}
|
||||
data-test="host"
|
||||
/>
|
||||
<LabeledInput
|
||||
label="Port"
|
||||
placeholder="5432"
|
||||
onChange={e =>
|
||||
connectionDBStateDispatch({
|
||||
type: 'UPDATE_DB_PORT',
|
||||
data: e.target.value,
|
||||
})
|
||||
}
|
||||
value={connectionDBState.connectionParamState.port}
|
||||
data-test="port"
|
||||
/>
|
||||
<LabeledInput
|
||||
label="Username"
|
||||
placeholder="postgres_user"
|
||||
onChange={e =>
|
||||
connectionDBStateDispatch({
|
||||
type: 'UPDATE_DB_USERNAME',
|
||||
data: e.target.value,
|
||||
})
|
||||
}
|
||||
value={connectionDBState.connectionParamState.username}
|
||||
data-test="username"
|
||||
/>
|
||||
<LabeledInput
|
||||
label="Password"
|
||||
key="connect-db-password"
|
||||
type="password"
|
||||
placeholder="postgrespassword"
|
||||
onChange={e =>
|
||||
connectionDBStateDispatch({
|
||||
type: 'UPDATE_DB_PASSWORD',
|
||||
data: e.target.value,
|
||||
})
|
||||
}
|
||||
value={connectionDBState.connectionParamState.password}
|
||||
data-test="password"
|
||||
/>
|
||||
<LabeledInput
|
||||
key="connect-db-database-name"
|
||||
label="Database Name"
|
||||
placeholder="postgres"
|
||||
onChange={e =>
|
||||
connectionDBStateDispatch({
|
||||
type: 'UPDATE_DB_DATABASE_NAME',
|
||||
data: e.target.value,
|
||||
})
|
||||
}
|
||||
value={connectionDBState.connectionParamState.database}
|
||||
data-test="database-name"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{getSupportedDrivers('connectDbForm.connectionSettings').includes(
|
||||
connectionDBState.dbType
|
||||
) && (
|
||||
<div className={styles.connection_settings_layout}>
|
||||
<div className={styles.connection_settings_header}>
|
||||
<a
|
||||
href="#"
|
||||
style={{ textDecoration: 'none' }}
|
||||
onClick={toggleConnectionParams(!currentConnectionParamState)}
|
||||
>
|
||||
{currentConnectionParamState ? (
|
||||
<i className="fa fa-caret-down" />
|
||||
) : (
|
||||
<i className="fa fa-caret-right" />
|
||||
)}
|
||||
{' '}
|
||||
Connection Settings
|
||||
</a>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
{currentConnectionParamState ? (
|
||||
<div className={styles.connection_settings_form}>
|
||||
<div className={styles.connection_settings_form_input_layout}>
|
||||
<LabeledInput
|
||||
label="Max Connections"
|
||||
type="number"
|
||||
className={`form-control ${styles.connnection_settings_form_input}`}
|
||||
placeholder="50"
|
||||
value={
|
||||
connectionDBState.connectionSettings?.max_connections ??
|
||||
undefined
|
||||
}
|
||||
onChange={e =>
|
||||
connectionDBStateDispatch({
|
||||
type: 'UPDATE_MAX_CONNECTIONS',
|
||||
data: e.target.value,
|
||||
})
|
||||
}
|
||||
min="0"
|
||||
boldlabel
|
||||
data-test="max-connections"
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.connection_settings_form_input_layout}>
|
||||
<LabeledInput
|
||||
label="Idle Timeout"
|
||||
type="number"
|
||||
className={`form-control ${styles.connnection_settings_form_input}`}
|
||||
placeholder="180"
|
||||
value={
|
||||
connectionDBState.connectionSettings?.idle_timeout ??
|
||||
undefined
|
||||
}
|
||||
onChange={e =>
|
||||
connectionDBStateDispatch({
|
||||
type: 'UPDATE_IDLE_TIMEOUT',
|
||||
data: e.target.value,
|
||||
})
|
||||
}
|
||||
min="0"
|
||||
boldlabel
|
||||
data-test="idle-timeout"
|
||||
/>
|
||||
</div>
|
||||
{getSupportedDrivers('connectDbForm.retries').includes(
|
||||
connectionDBState.dbType
|
||||
) && (
|
||||
<div className={styles.connection_settings_form_input_layout}>
|
||||
<LabeledInput
|
||||
label="Retries"
|
||||
type="number"
|
||||
className={`form-control ${styles.connnection_settings_form_input}`}
|
||||
placeholder="1"
|
||||
value={
|
||||
connectionDBState.connectionSettings?.retries ??
|
||||
undefined
|
||||
}
|
||||
onChange={e =>
|
||||
connectionDBStateDispatch({
|
||||
type: 'UPDATE_RETRIES',
|
||||
data: e.target.value,
|
||||
})
|
||||
}
|
||||
min="0"
|
||||
boldlabel
|
||||
data-test="retries"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
@ -11,7 +11,6 @@ import { connect, ConnectedProps } from 'react-redux';
|
||||
import Tabbed from './TabbedDataSourceConnection';
|
||||
import { ReduxState } from '../../../../types';
|
||||
import { mapDispatchToPropsEmpty } from '../../../Common/utils/reactUtils';
|
||||
import Button from '../../../Common/Button';
|
||||
import { showErrorNotification } from '../../Common/Notification';
|
||||
import _push from '../push';
|
||||
import {
|
||||
@ -23,11 +22,14 @@ import {
|
||||
defaultState,
|
||||
makeReadReplicaConnectionObject,
|
||||
} from './state';
|
||||
import { getDatasourceURL, getErrorMessageFromMissingFields } from './utils';
|
||||
import ConnectDatabaseForm from './ConnectDBForm';
|
||||
import {
|
||||
getDatasourceURL,
|
||||
getErrorMessageFromMissingFields,
|
||||
parsePgUrl,
|
||||
} from './utils';
|
||||
import ReadReplicaForm from './ReadReplicaForm';
|
||||
|
||||
import styles from './DataSources.scss';
|
||||
import EditDataSource from './EditDataSource';
|
||||
import DataSourceFormWrapper from './DataSourceFromWrapper';
|
||||
import { getSupportedDrivers } from '../../../../dataSources';
|
||||
|
||||
interface ConnectDatabaseProps extends InjectedProps {}
|
||||
@ -60,7 +62,7 @@ const ConnectDatabase: React.FC<ConnectDatabaseProps> = props => {
|
||||
connectionTypes.DATABASE_URL
|
||||
);
|
||||
|
||||
const { sources = [], pathname = '' } = props;
|
||||
const { sources = [], pathname } = props;
|
||||
const isEditState =
|
||||
pathname.includes('edit') || pathname.indexOf('edit') !== -1;
|
||||
const paths = pathname.split('/');
|
||||
@ -71,19 +73,74 @@ const ConnectDatabase: React.FC<ConnectDatabaseProps> = props => {
|
||||
|
||||
useEffect(() => {
|
||||
if (isEditState && currentSourceInfo) {
|
||||
const connectionInfo = currentSourceInfo.configuration?.connection_info;
|
||||
const databaseUrl =
|
||||
connectionInfo?.database_url || connectionInfo?.connection_string;
|
||||
connectDBDispatch({
|
||||
type: 'INIT',
|
||||
data: {
|
||||
name: currentSourceInfo.name,
|
||||
driver: currentSourceInfo.kind ?? 'postgres',
|
||||
databaseUrl: getDatasourceURL(
|
||||
currentSourceInfo?.configuration?.connection_info?.database_url
|
||||
databaseUrl ?? connectionInfo?.connection_string
|
||||
),
|
||||
connectionSettings:
|
||||
currentSourceInfo.configuration?.connection_info?.pool_settings ??
|
||||
{},
|
||||
connectionSettings: connectionInfo?.pool_settings ?? {},
|
||||
},
|
||||
});
|
||||
|
||||
if (
|
||||
typeof databaseUrl === 'string' &&
|
||||
currentSourceInfo.kind === 'postgres'
|
||||
) {
|
||||
const p = parsePgUrl(databaseUrl);
|
||||
connectDBDispatch({
|
||||
type: 'UPDATE_PARAM_STATE',
|
||||
data: {
|
||||
host: p.host?.replace(/:\d*$/, '') ?? '',
|
||||
port: p.port ?? '',
|
||||
database: p.pathname?.slice(1) ?? '',
|
||||
username: p.username ?? '',
|
||||
password: p.password ?? '',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (typeof databaseUrl !== 'string' && databaseUrl?.from_env) {
|
||||
changeConnectionType(connectionTypes.ENV_VAR);
|
||||
connectDBDispatch({
|
||||
type: 'UPDATE_DB_URL_ENV_VAR',
|
||||
data: databaseUrl.from_env,
|
||||
});
|
||||
connectDBDispatch({
|
||||
type: 'UPDATE_DB_URL',
|
||||
data: '',
|
||||
});
|
||||
}
|
||||
|
||||
if (currentSourceInfo?.kind === 'bigquery') {
|
||||
const conf = currentSourceInfo.configuration;
|
||||
connectDBDispatch({
|
||||
type: 'UPDATE_DB_BIGQUERY_DATASETS',
|
||||
data: conf?.datasets?.join(', ') ?? '',
|
||||
});
|
||||
connectDBDispatch({
|
||||
type: 'UPDATE_DB_BIGQUERY_PROJECT_ID',
|
||||
data: conf?.project_id ?? '',
|
||||
});
|
||||
if (conf?.service_account?.from_env) {
|
||||
changeConnectionType(connectionTypes.ENV_VAR);
|
||||
connectDBDispatch({
|
||||
type: 'UPDATE_DB_URL_ENV_VAR',
|
||||
data: conf?.service_account?.from_env,
|
||||
});
|
||||
} else {
|
||||
changeConnectionType(connectionTypes.CONNECTION_PARAMS);
|
||||
connectDBDispatch({
|
||||
type: 'UPDATE_DB_BIGQUERY_SERVICE_ACCOUNT',
|
||||
data: JSON.stringify(conf?.service_account, null, 2) ?? '{}',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [isEditState, currentSourceInfo]);
|
||||
|
||||
@ -115,10 +172,6 @@ const ConnectDatabase: React.FC<ConnectDatabaseProps> = props => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isEditState) {
|
||||
// TODO: server to provide API
|
||||
}
|
||||
|
||||
// TODO: check if permitted, if not pass undefined
|
||||
const read_replicas = readReplicasState.map(replica =>
|
||||
makeReadReplicaConnectionObject(replica)
|
||||
@ -148,7 +201,8 @@ const ConnectDatabase: React.FC<ConnectDatabaseProps> = props => {
|
||||
connectionTypes.DATABASE_URL,
|
||||
connectDBInputState,
|
||||
onSuccessConnectDBCb,
|
||||
read_replicas
|
||||
read_replicas,
|
||||
isEditState
|
||||
)
|
||||
.then(() => setLoading(false))
|
||||
.catch(() => setLoading(false));
|
||||
@ -175,7 +229,8 @@ const ConnectDatabase: React.FC<ConnectDatabaseProps> = props => {
|
||||
connectionTypes.ENV_VAR,
|
||||
connectDBInputState,
|
||||
onSuccessConnectDBCb,
|
||||
read_replicas
|
||||
read_replicas,
|
||||
isEditState
|
||||
)
|
||||
.then(() => setLoading(false))
|
||||
.catch(() => setLoading(false));
|
||||
@ -211,7 +266,8 @@ const ConnectDatabase: React.FC<ConnectDatabaseProps> = props => {
|
||||
connectionTypes.CONNECTION_PARAMS,
|
||||
connectDBInputState,
|
||||
onSuccessConnectDBCb,
|
||||
read_replicas
|
||||
read_replicas,
|
||||
isEditState
|
||||
)
|
||||
.then(() => setLoading(false))
|
||||
.catch(() => setLoading(false));
|
||||
@ -255,18 +311,36 @@ const ConnectDatabase: React.FC<ConnectDatabaseProps> = props => {
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Tabbed tabName="connect">
|
||||
<form
|
||||
onSubmit={onSubmit}
|
||||
className={`${styles.connect_db_content} ${styles.connect_form_width}`}
|
||||
>
|
||||
<ConnectDatabaseForm
|
||||
if (isEditState) {
|
||||
return (
|
||||
<EditDataSource>
|
||||
<DataSourceFormWrapper
|
||||
connectionDBState={connectDBInputState}
|
||||
connectionDBStateDispatch={connectDBDispatch}
|
||||
connectionTypeState={connectionType}
|
||||
updateConnectionTypeRadio={onChangeConnectionType}
|
||||
changeConnectionType={changeConnectionType}
|
||||
isEditState={isEditState}
|
||||
loading={loading}
|
||||
onSubmit={onSubmit}
|
||||
title="Edit Data Source"
|
||||
/>
|
||||
</EditDataSource>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Tabbed tabName="connect">
|
||||
<DataSourceFormWrapper
|
||||
connectionDBState={connectDBInputState}
|
||||
connectionDBStateDispatch={connectDBDispatch}
|
||||
connectionTypeState={connectionType}
|
||||
updateConnectionTypeRadio={onChangeConnectionType}
|
||||
changeConnectionType={changeConnectionType}
|
||||
isEditState={isEditState}
|
||||
loading={loading}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
{/* Should be rendered only on Pro and Cloud Console */}
|
||||
{getSupportedDrivers('connectDbForm.read_replicas').includes(
|
||||
connectDBInputState.dbType
|
||||
@ -284,23 +358,7 @@ const ConnectDatabase: React.FC<ConnectDatabaseProps> = props => {
|
||||
onClickSaveReadReplicaCb={onClickSaveReadReplicaForm}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className={styles.add_button_layout}>
|
||||
<Button
|
||||
size="large"
|
||||
color="yellow"
|
||||
type="submit"
|
||||
style={{
|
||||
width: '70%',
|
||||
...(loading && { cursor: 'progress' }),
|
||||
}}
|
||||
disabled={loading}
|
||||
data-test="connect-database-btn"
|
||||
>
|
||||
{!isEditState ? 'Connect Database' : 'Edit Connection'}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</DataSourceFormWrapper>
|
||||
</Tabbed>
|
||||
);
|
||||
};
|
||||
@ -311,7 +369,7 @@ const mapStateToProps = (state: ReduxState) => {
|
||||
currentSchema: state.tables.currentSchema,
|
||||
sources: state.metadata.metadataObject?.sources ?? [],
|
||||
dbConnection: state.tables.dbConnection,
|
||||
pathname: state?.routing?.locationBeforeTransitions?.pathname,
|
||||
pathname: state?.routing?.locationBeforeTransitions?.pathname ?? '',
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -0,0 +1,43 @@
|
||||
import React, { FormEvent } from 'react';
|
||||
import { Button } from '../../../Common';
|
||||
import ConnectDatabaseForm, { ConnectDatabaseFormProps } from './ConnectDBForm';
|
||||
import styles from './DataSources.scss';
|
||||
|
||||
interface DataSourceFormWrapperProps extends ConnectDatabaseFormProps {
|
||||
loading: boolean;
|
||||
onSubmit: (e: FormEvent<HTMLFormElement>) => void;
|
||||
}
|
||||
|
||||
const DataSourceFormWrapper: React.FC<DataSourceFormWrapperProps> = ({
|
||||
onSubmit,
|
||||
loading,
|
||||
isEditState,
|
||||
children,
|
||||
...props
|
||||
}) => {
|
||||
return (
|
||||
<form
|
||||
onSubmit={onSubmit}
|
||||
className={`${styles.connect_db_content} ${styles.connect_form_width}`}
|
||||
>
|
||||
<ConnectDatabaseForm isEditState={isEditState} {...props} />
|
||||
{children}
|
||||
<div className={styles.add_button_layout}>
|
||||
<Button
|
||||
size="large"
|
||||
color="yellow"
|
||||
type="submit"
|
||||
style={{
|
||||
width: '70%',
|
||||
...(loading && { cursor: 'progress' }),
|
||||
}}
|
||||
disabled={loading}
|
||||
data-test="connect-database-btn"
|
||||
>
|
||||
{!isEditState ? 'Connect Database' : 'Update Connection'}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
export default DataSourceFormWrapper;
|
@ -54,3 +54,8 @@
|
||||
margin-right: 8px;
|
||||
color: rgb(214, 29, 29);
|
||||
}
|
||||
|
||||
.info_label {
|
||||
margin-bottom: 10px;
|
||||
color: #4d4d4db3;
|
||||
}
|
||||
|
@ -0,0 +1,35 @@
|
||||
import React from 'react';
|
||||
import Helmet from 'react-helmet';
|
||||
import BreadCrumb from '../../../Common/Layout/BreadCrumb/BreadCrumb';
|
||||
import { RightContainer } from '../../../Common/Layout/RightContainer';
|
||||
import styles from './DataSources.scss';
|
||||
|
||||
const appPrefix = '/data';
|
||||
const breadCrumbs = [
|
||||
{
|
||||
title: 'Data',
|
||||
url: appPrefix,
|
||||
},
|
||||
{
|
||||
title: 'Data Manager',
|
||||
url: `${appPrefix}/manage`,
|
||||
},
|
||||
{
|
||||
title: 'Edit Data Source',
|
||||
url: '',
|
||||
},
|
||||
];
|
||||
|
||||
const EditDataSource: React.FC = ({ children }) => {
|
||||
return (
|
||||
<RightContainer>
|
||||
<Helmet title="Edit Data Source - Hasura" />
|
||||
<div className={styles.add_pad_left_mid}>
|
||||
<BreadCrumb breadCrumbs={breadCrumbs} />
|
||||
</div>
|
||||
{children}
|
||||
</RightContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditDataSource;
|
@ -1,4 +1,3 @@
|
||||
import { DataSource } from '../../../../metadata/types';
|
||||
import { Driver } from '../../../../dataSources';
|
||||
|
||||
export const parseURI = (url: string) => {
|
||||
@ -31,14 +30,6 @@ export const parseURI = (url: string) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const getHostFromConnectionString = (datasource: DataSource) => {
|
||||
if (typeof datasource.url === 'string' && datasource.driver === 'postgres') {
|
||||
const { hostname = null } = parseURI(datasource.url);
|
||||
return hostname;
|
||||
}
|
||||
// TODO: update this function with connection string for other databases
|
||||
return null;
|
||||
};
|
||||
export const makeConnectionStringFromConnectionParams = ({
|
||||
dbType,
|
||||
host,
|
||||
|
@ -7,10 +7,9 @@ import styles from '../../../Common/Common.scss';
|
||||
|
||||
const appPrefix = '/data';
|
||||
|
||||
const TabbedDSConnection: React.FC<{ tabName: 'create' | 'connect' }> = ({
|
||||
children,
|
||||
tabName,
|
||||
}) => {
|
||||
const TabbedDSConnection: React.FC<{
|
||||
tabName: 'create' | 'connect';
|
||||
}> = ({ children, tabName }) => {
|
||||
const breadCrumbs = [
|
||||
{
|
||||
title: 'Data',
|
||||
|
@ -30,7 +30,7 @@ export type ConnectDBState = {
|
||||
connectionParamState: ConnectionParams;
|
||||
databaseURLState: {
|
||||
dbURL: string;
|
||||
serviceAccountFile: string;
|
||||
serviceAccount: string;
|
||||
projectId: string;
|
||||
datasets: string;
|
||||
};
|
||||
@ -52,7 +52,7 @@ export const defaultState: ConnectDBState = {
|
||||
},
|
||||
databaseURLState: {
|
||||
dbURL: '',
|
||||
serviceAccountFile: '',
|
||||
serviceAccount: '',
|
||||
projectId: '',
|
||||
datasets: '',
|
||||
},
|
||||
@ -93,11 +93,12 @@ export const connectDataSource = (
|
||||
typeConnection: string,
|
||||
currentState: ConnectDBState,
|
||||
cb: () => void,
|
||||
replicas?: Omit<SourceConnectionInfo, 'connection_string'>[]
|
||||
replicas?: Omit<SourceConnectionInfo, 'connection_string'>[],
|
||||
isEditState = false
|
||||
) => {
|
||||
let databaseURL: string | { from_env: string } =
|
||||
currentState.dbType === 'bigquery'
|
||||
? currentState.databaseURLState.serviceAccountFile.trim()
|
||||
? currentState.databaseURLState.serviceAccount.trim()
|
||||
: currentState.databaseURLState.dbURL.trim();
|
||||
if (
|
||||
typeConnection === connectionTypes.ENV_VAR &&
|
||||
@ -108,6 +109,7 @@ export const connectDataSource = (
|
||||
databaseURL = { from_env: currentState.envVarState.envVar.trim() };
|
||||
} else if (
|
||||
typeConnection === connectionTypes.CONNECTION_PARAMS &&
|
||||
currentState.dbType !== 'bigquery' &&
|
||||
getSupportedDrivers('connectDbForm.connectionParameters').includes(
|
||||
currentState.dbType
|
||||
)
|
||||
@ -126,6 +128,7 @@ export const connectDataSource = (
|
||||
name: currentState.displayName.trim(),
|
||||
dbUrl: databaseURL,
|
||||
connection_pool_settings: currentState.connectionSettings,
|
||||
replace_configuration: isEditState,
|
||||
bigQuery: {
|
||||
projectId: currentState.databaseURLState.projectId,
|
||||
datasets: currentState.databaseURLState.datasets,
|
||||
@ -148,9 +151,10 @@ export type ConnectDBActions =
|
||||
connectionSettings: ConnectionSettings;
|
||||
};
|
||||
}
|
||||
| { type: 'UPDATE_PARAM_STATE'; data: ConnectionParams }
|
||||
| { type: 'UPDATE_DISPLAY_NAME'; data: string }
|
||||
| { type: 'UPDATE_DB_URL'; data: string }
|
||||
| { type: 'UPDATE_DB_BIGQUERY_SERVICE_ACCOUNT_FILE'; data: string }
|
||||
| { type: 'UPDATE_DB_BIGQUERY_SERVICE_ACCOUNT'; data: string }
|
||||
| { type: 'UPDATE_DB_BIGQUERY_PROJECT_ID'; data: string }
|
||||
| { type: 'UPDATE_DB_BIGQUERY_DATASETS'; data: string }
|
||||
| { type: 'UPDATE_DB_URL_ENV_VAR'; data: string }
|
||||
@ -182,6 +186,11 @@ export const connectDBReducer = (
|
||||
},
|
||||
connectionSettings: action.data.connectionSettings,
|
||||
};
|
||||
case 'UPDATE_PARAM_STATE':
|
||||
return {
|
||||
...state,
|
||||
connectionParamState: action.data,
|
||||
};
|
||||
case 'UPDATE_DISPLAY_NAME':
|
||||
return {
|
||||
...state,
|
||||
@ -280,12 +289,12 @@ export const connectDBReducer = (
|
||||
...state,
|
||||
connectionSettings: action.data,
|
||||
};
|
||||
case 'UPDATE_DB_BIGQUERY_SERVICE_ACCOUNT_FILE':
|
||||
case 'UPDATE_DB_BIGQUERY_SERVICE_ACCOUNT':
|
||||
return {
|
||||
...state,
|
||||
databaseURLState: {
|
||||
...state.databaseURLState,
|
||||
serviceAccountFile: action.data,
|
||||
serviceAccount: action.data,
|
||||
},
|
||||
};
|
||||
case 'UPDATE_DB_BIGQUERY_DATASETS':
|
||||
|
@ -38,6 +38,31 @@ export const getDatasourceURL = (
|
||||
return link.from_env.toString();
|
||||
};
|
||||
|
||||
export function parsePgUrl(
|
||||
url: string
|
||||
): Partial<Omit<URL, 'searchParams' | 'toJSON'>> {
|
||||
try {
|
||||
const protocol = new URL(url).protocol;
|
||||
const newUrl = url.replace(protocol, 'http://');
|
||||
const parsed = new URL(newUrl);
|
||||
return {
|
||||
origin: parsed.origin.replace('http:', protocol),
|
||||
hash: parsed.hash,
|
||||
host: parsed.host,
|
||||
hostname: parsed.hostname,
|
||||
port: parsed.port,
|
||||
href: parsed.href.replace('http:', protocol),
|
||||
password: parsed.password,
|
||||
pathname: parsed.pathname,
|
||||
search: parsed.search,
|
||||
username: parsed.username,
|
||||
protocol,
|
||||
};
|
||||
} catch (error) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
type TableType = Record<string, { table_type: Table['table_type'] }>;
|
||||
type SchemaType = Record<string, TableType>;
|
||||
type SourceSchemasType = Record<string, SchemaType>;
|
||||
@ -65,20 +90,3 @@ export const canReUseTableTypes = (
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
export const readFile = (
|
||||
file: File | null,
|
||||
callback: (content: string) => void
|
||||
) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = event => {
|
||||
const content = event.target!.result as string;
|
||||
callback(content);
|
||||
};
|
||||
|
||||
reader.onerror = event => {
|
||||
console.error(`File could not be read! Code ${event.target!.error!.code}`);
|
||||
};
|
||||
|
||||
if (file) reader.readAsText(file);
|
||||
};
|
||||
|
@ -65,7 +65,6 @@ const trackAllItems = (sql, isMigration, migrationName, source, driver) => (
|
||||
const tableDef = { name, schema };
|
||||
const { allSchemas } = getState().tables;
|
||||
const table = findTable(allSchemas, tableDef);
|
||||
console.log({ table });
|
||||
req = getTrackTableQuery({
|
||||
tableDef,
|
||||
source,
|
||||
|
@ -29,7 +29,7 @@ import CollapsibleToggle from './CollapsibleToggle';
|
||||
type DatabaseListItemProps = {
|
||||
dataSource: DataSource;
|
||||
inconsistentObjects: InjectedProps['inconsistentObjects'];
|
||||
// onEdit: (dbName: string) => void;
|
||||
onEdit: (dbName: string) => void;
|
||||
onReload: (name: string, driver: Driver, cb: () => void) => void;
|
||||
onRemove: (name: string, driver: Driver, cb: () => void) => void;
|
||||
pushRoute: (route: string) => void;
|
||||
@ -38,7 +38,7 @@ type DatabaseListItemProps = {
|
||||
};
|
||||
|
||||
const DatabaseListItem: React.FC<DatabaseListItemProps> = ({
|
||||
// onEdit,
|
||||
onEdit,
|
||||
pushRoute,
|
||||
onReload,
|
||||
onRemove,
|
||||
@ -102,7 +102,7 @@ const DatabaseListItem: React.FC<DatabaseListItemProps> = ({
|
||||
<Button
|
||||
size="xs"
|
||||
color="white"
|
||||
style={{ marginRight: '10px' }}
|
||||
className={styles.add_mar_right_mid}
|
||||
onClick={viewDB}
|
||||
disabled={isInconsistentDataSource}
|
||||
>
|
||||
@ -120,6 +120,16 @@ const DatabaseListItem: React.FC<DatabaseListItemProps> = ({
|
||||
>
|
||||
{reloading ? 'Reloading...' : 'Reload'}
|
||||
</Button>
|
||||
<Button
|
||||
size="xs"
|
||||
color="white"
|
||||
onClick={() => {
|
||||
onEdit(dataSource.name);
|
||||
}}
|
||||
className={styles.add_mar_left_mid}
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
<Button
|
||||
className={`${styles.text_red}`}
|
||||
size="xs"
|
||||
@ -249,9 +259,9 @@ const ManageDatabase: React.FC<ManageDatabaseProps> = ({
|
||||
if (route) dispatch(_push(route));
|
||||
};
|
||||
|
||||
// const onEdit = (dbName: string) => {
|
||||
// dispatch(_push(`/data/manage/edit/${dbName}`));
|
||||
// };
|
||||
const onEdit = (dbName: string) => {
|
||||
dispatch(_push(`/data/manage/edit/${dbName}`));
|
||||
};
|
||||
|
||||
return (
|
||||
<RightContainer>
|
||||
@ -286,6 +296,7 @@ const ManageDatabase: React.FC<ManageDatabaseProps> = ({
|
||||
dataSource={data}
|
||||
inconsistentObjects={inconsistentObjects}
|
||||
pushRoute={pushRoute}
|
||||
onEdit={onEdit}
|
||||
onReload={onReload}
|
||||
onRemove={onRemove}
|
||||
dispatch={dispatch}
|
||||
|
@ -7,12 +7,14 @@ export interface JSONEditorProps {
|
||||
initData: string;
|
||||
onChange: (v: string) => void;
|
||||
data: string;
|
||||
minLines?: number;
|
||||
}
|
||||
|
||||
const JSONEditor: React.FC<JSONEditorProps> = ({
|
||||
initData,
|
||||
onChange,
|
||||
data,
|
||||
minLines,
|
||||
}) => {
|
||||
const [value, setValue] = useState(initData || data || '');
|
||||
const [annotations, setAnnotations] = useState<IAnnotation[]>([]);
|
||||
@ -56,6 +58,7 @@ const JSONEditor: React.FC<JSONEditorProps> = ({
|
||||
onChange={onEditorValueChange}
|
||||
theme="github"
|
||||
height="5em"
|
||||
minLines={minLines || 1}
|
||||
maxLines={15}
|
||||
width="100%"
|
||||
showPrintMargin={false}
|
||||
|
@ -153,10 +153,12 @@ export const supportedFeatures: SupportedFeaturesType = {
|
||||
},
|
||||
connectDbForm: {
|
||||
enabled: globals.consoleType !== 'cloud',
|
||||
connectionParameters: false,
|
||||
databaseURL: true,
|
||||
connectionParameters: true,
|
||||
databaseURL: false,
|
||||
environmentVariable: true,
|
||||
read_replicas: false,
|
||||
connectionSettings: false,
|
||||
retries: false,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -163,6 +163,8 @@ export const supportedFeatures: SupportedFeaturesType = {
|
||||
databaseURL: true,
|
||||
environmentVariable: true,
|
||||
read_replicas: false,
|
||||
connectionSettings: true,
|
||||
retries: false,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -586,6 +586,8 @@ export const supportedFeatures: SupportedFeaturesType = {
|
||||
databaseURL: true,
|
||||
environmentVariable: true,
|
||||
read_replicas: true,
|
||||
connectionSettings: true,
|
||||
retries: true,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -294,6 +294,8 @@ export type SupportedFeaturesType = {
|
||||
databaseURL: boolean;
|
||||
environmentVariable: boolean;
|
||||
read_replicas: boolean;
|
||||
connectionSettings: boolean;
|
||||
retries: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -123,6 +123,7 @@ export interface AddDataSourceRequest {
|
||||
idle_timeout?: number; // in seconds
|
||||
retries?: number;
|
||||
};
|
||||
replace_configuration?: boolean;
|
||||
bigQuery: {
|
||||
projectId: string;
|
||||
datasets: string;
|
||||
@ -255,7 +256,7 @@ export const addDataSource = (
|
||||
headers: dataHeaders,
|
||||
body: JSON.stringify(query),
|
||||
};
|
||||
|
||||
const isEdit = data.payload.replace_configuration;
|
||||
return dispatch(requestAction(Endpoints.metadata, options))
|
||||
.then(() => {
|
||||
dispatch({
|
||||
@ -273,7 +274,9 @@ export const addDataSource = (
|
||||
dispatch(
|
||||
showNotification(
|
||||
{
|
||||
title: 'Database added successfully!',
|
||||
title: `Data source ${
|
||||
!isEdit ? 'added' : 'updated'
|
||||
} successfully!`,
|
||||
level: 'success',
|
||||
autoDismiss: 0,
|
||||
action: {
|
||||
@ -291,9 +294,17 @@ export const addDataSource = (
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
dispatch(_push('/data/manage/connect'));
|
||||
if (!isEdit) {
|
||||
dispatch(_push('/data/manage/connect'));
|
||||
}
|
||||
if (!skipNotification) {
|
||||
dispatch(showErrorNotification('Add data source failed', null, err));
|
||||
dispatch(
|
||||
showErrorNotification(
|
||||
`${!isEdit ? 'Add' : 'Updating'} data source failed`,
|
||||
null,
|
||||
err
|
||||
)
|
||||
);
|
||||
}
|
||||
return err;
|
||||
});
|
||||
@ -340,55 +351,6 @@ export const removeDataSource = (
|
||||
});
|
||||
};
|
||||
|
||||
export const editDataSource = (
|
||||
oldName: string | undefined,
|
||||
data: AddDataSourceRequest['data'],
|
||||
onSuccessCb: () => void
|
||||
): Thunk<Promise<void | ReduxState>, MetadataActions> => dispatch => {
|
||||
return dispatch(
|
||||
removeDataSource(
|
||||
{ driver: data.driver, name: oldName ?? data.payload.name },
|
||||
true
|
||||
)
|
||||
)
|
||||
.then(() => {
|
||||
// FIXME?: There might be a problem when or if the metadata is inconsistent,
|
||||
// we should be providing a better error message for the same
|
||||
dispatch(
|
||||
addDataSource(
|
||||
data,
|
||||
() => {
|
||||
dispatch(
|
||||
showSuccessNotification(
|
||||
'Successfully updated datasource details.'
|
||||
)
|
||||
);
|
||||
onSuccessCb();
|
||||
},
|
||||
[],
|
||||
true
|
||||
)
|
||||
).catch(err => {
|
||||
console.error(err);
|
||||
dispatch(
|
||||
showErrorNotification(
|
||||
'Failed to edit data source',
|
||||
'There was a problem in editing the details of the datasource'
|
||||
)
|
||||
);
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
dispatch(
|
||||
showErrorNotification(
|
||||
'Failed to edit data source',
|
||||
'There was a problem in editing the details of the datasource'
|
||||
)
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
export const replaceMetadata = (
|
||||
newMetadata: HasuraMetadataV3,
|
||||
successCb: () => void,
|
||||
|
@ -11,6 +11,7 @@ export const addSource = (
|
||||
idle_timeout?: number;
|
||||
retries?: number;
|
||||
};
|
||||
replace_configuration?: boolean;
|
||||
bigQuery: {
|
||||
projectId: string;
|
||||
datasets: string;
|
||||
@ -19,6 +20,7 @@ export const addSource = (
|
||||
// supported only for PG sources at the moment
|
||||
replicas?: Omit<SourceConnectionInfo, 'connection_string'>[]
|
||||
) => {
|
||||
const replace_configuration = payload.replace_configuration ?? false;
|
||||
if (driver === 'mssql') {
|
||||
return {
|
||||
type: 'mssql_add_source',
|
||||
@ -30,20 +32,26 @@ export const addSource = (
|
||||
pool_settings: payload.connection_pool_settings,
|
||||
},
|
||||
},
|
||||
replace_configuration,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (driver === 'bigquery') {
|
||||
const service_account =
|
||||
typeof payload.dbUrl === 'string'
|
||||
? JSON.parse(payload.dbUrl)
|
||||
: payload.dbUrl;
|
||||
return {
|
||||
type: 'bigquery_add_source',
|
||||
args: {
|
||||
name: payload.name,
|
||||
configuration: {
|
||||
service_account: payload.dbUrl,
|
||||
service_account,
|
||||
project_id: payload.bigQuery.projectId,
|
||||
datasets: payload.bigQuery.datasets.split(',').map(d => d.trim()),
|
||||
},
|
||||
replace_configuration,
|
||||
},
|
||||
};
|
||||
}
|
||||
@ -59,6 +67,7 @@ export const addSource = (
|
||||
},
|
||||
read_replicas: replicas?.length ? replicas : null,
|
||||
},
|
||||
replace_configuration,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
@ -884,7 +884,7 @@ export interface RestEndpointEntry {
|
||||
|
||||
export interface SourceConnectionInfo {
|
||||
// used for SQL Server
|
||||
connection_string: string;
|
||||
connection_string: string | { from_env: string };
|
||||
// used for Postgres
|
||||
database_url: string | { from_env: string };
|
||||
pool_settings: {
|
||||
|
@ -105,7 +105,8 @@ Remove a database with name ``pg1``:
|
||||
{
|
||||
"type": "pg_drop_source",
|
||||
"args": {
|
||||
"name": "pg1"
|
||||
"name": "pg1",
|
||||
"cascade": true
|
||||
}
|
||||
}
|
||||
|
||||
@ -125,3 +126,204 @@ Args syntax
|
||||
- true
|
||||
- :ref:`SourceName <SourceName>`
|
||||
- Name of the Postgres database
|
||||
* - cascade
|
||||
- false
|
||||
- Boolean
|
||||
- When set to ``true``, the effect (if possible) is cascaded to any metadata dependent objects (relationships, permissions etc.) from other sources (default: ``false``)
|
||||
|
||||
mssql_add_source
|
||||
----------------
|
||||
|
||||
``mssql_add_source`` is used to connect a MS SQL Server database to Hasura.
|
||||
|
||||
Add a database with name ``mssql1``:
|
||||
|
||||
.. code-block:: http
|
||||
|
||||
POST /v1/metadata HTTP/1.1
|
||||
Content-Type: application/json
|
||||
X-Hasura-Role: admin
|
||||
|
||||
{
|
||||
"type": "mssql_add_source",
|
||||
"args": {
|
||||
"name": "mssql1",
|
||||
"configuration": {
|
||||
"connection_info": {
|
||||
"connection_string": {
|
||||
"from_env": "<CONN_STRING_ENV_VAR>"
|
||||
},
|
||||
"pool_settings": {
|
||||
"max_connections": 50,
|
||||
"idle_timeout": 180
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.. _mssql_add_source_syntax:
|
||||
|
||||
Args syntax
|
||||
^^^^^^^^^^^
|
||||
|
||||
.. list-table::
|
||||
:header-rows: 1
|
||||
|
||||
* - Key
|
||||
- Required
|
||||
- Schema
|
||||
- Description
|
||||
* - name
|
||||
- true
|
||||
- :ref:`SourceName <SourceName>`
|
||||
- Name of the MS SQL Server database
|
||||
* - configuration
|
||||
- true
|
||||
- :ref:`MsSQLConfiguration <MsSQLConfiguration>`
|
||||
- Database connection configuration
|
||||
* - replace_configuration
|
||||
- false
|
||||
- Boolean
|
||||
- If set to ``true`` the configuration will be replaced if the source with
|
||||
given name already exists (default: ``false``)
|
||||
|
||||
.. _mssql_drop_source:
|
||||
|
||||
mssql_drop_source
|
||||
-----------------
|
||||
|
||||
``mssql_drop_source`` is used to remove a MS SQL Server database from Hasura.
|
||||
|
||||
Remove a database with name ``mssql1``:
|
||||
|
||||
.. code-block:: http
|
||||
|
||||
POST /v1/metadata HTTP/1.1
|
||||
Content-Type: application/json
|
||||
X-Hasura-Role: admin
|
||||
|
||||
{
|
||||
"type": "mssql_drop_source",
|
||||
"args": {
|
||||
"name": "mssql1"
|
||||
}
|
||||
}
|
||||
|
||||
.. _mssql_drop_source_syntax:
|
||||
|
||||
Args syntax
|
||||
^^^^^^^^^^^
|
||||
|
||||
.. list-table::
|
||||
:header-rows: 1
|
||||
|
||||
* - Key
|
||||
- Required
|
||||
- Schema
|
||||
- Description
|
||||
* - name
|
||||
- true
|
||||
- :ref:`SourceName <SourceName>`
|
||||
- Name of the MS SQL Server database
|
||||
* - cascade
|
||||
- false
|
||||
- Boolean
|
||||
- When set to ``true``, the effect (if possible) is cascaded to any metadata dependent objects (relationships, permissions etc.) from other sources (default: ``false``)
|
||||
|
||||
|
||||
.. _bigquery_add_source:
|
||||
|
||||
bigquery_add_source
|
||||
-------------------
|
||||
|
||||
``bigquery_add_source`` is used to connect a BigQuery database to Hasura.
|
||||
|
||||
Add a database with name ``bigquery1``:
|
||||
|
||||
.. code-block:: http
|
||||
|
||||
POST /v1/metadata HTTP/1.1
|
||||
Content-Type: application/json
|
||||
X-Hasura-Role: admin
|
||||
|
||||
{
|
||||
"type": "bigquery_add_source",
|
||||
"args": {
|
||||
"name": "bigquery1",
|
||||
"configuration": {
|
||||
"service_account": "bigquery_service_account",
|
||||
"project_id": "bigquery_project_id",
|
||||
"datasets": "dataset1, dataset2"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.. _bigquery_add_source_syntax:
|
||||
|
||||
Args syntax
|
||||
^^^^^^^^^^^
|
||||
|
||||
.. list-table::
|
||||
:header-rows: 1
|
||||
|
||||
* - Key
|
||||
- Required
|
||||
- Schema
|
||||
- Description
|
||||
* - name
|
||||
- true
|
||||
- :ref:`SourceName <SourceName>`
|
||||
- Name of the BigQuery database
|
||||
* - configuration
|
||||
- true
|
||||
- :ref:`BigQueryConfiguration <BigQueryConfiguration>`
|
||||
- Database connection configuration
|
||||
* - replace_configuration
|
||||
- false
|
||||
- Boolean
|
||||
- If set to ``true`` the configuration will be replaced if the source with
|
||||
given name already exists (default: ``false``)
|
||||
|
||||
.. _bigquery_drop_source:
|
||||
|
||||
bigquery_drop_source
|
||||
--------------------
|
||||
|
||||
``bigquery_drop_source`` is used to remove a BigQuery database from Hasura.
|
||||
|
||||
Remove a database with name ``bigquery1``:
|
||||
|
||||
.. code-block:: http
|
||||
|
||||
POST /v1/metadata HTTP/1.1
|
||||
Content-Type: application/json
|
||||
X-Hasura-Role: admin
|
||||
|
||||
{
|
||||
"type": "bigquery_drop_source",
|
||||
"args": {
|
||||
"name": "bigquery1"
|
||||
}
|
||||
}
|
||||
|
||||
.. _bigquery_drop_source_syntax:
|
||||
|
||||
Args syntax
|
||||
^^^^^^^^^^^
|
||||
|
||||
.. list-table::
|
||||
:header-rows: 1
|
||||
|
||||
* - Key
|
||||
- Required
|
||||
- Schema
|
||||
- Description
|
||||
* - name
|
||||
- true
|
||||
- :ref:`SourceName <SourceName>`
|
||||
- Name of the BigQuery database
|
||||
* - cascade
|
||||
- false
|
||||
- Boolean
|
||||
- When set to ``true``, the effect (if possible) is cascaded to any metadata dependent objects (relationships, permissions etc.) from other sources (default: ``false``)
|
||||
|
@ -105,6 +105,50 @@ PGConfiguration
|
||||
- [PGSourceConnectionInfo_]
|
||||
- Optional list of read replica configuration *(supported only in cloud/enterprise versions)*
|
||||
|
||||
.. _MsSQLConfiguration:
|
||||
|
||||
MsSQLConfiguration
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. list-table::
|
||||
:header-rows: 1
|
||||
|
||||
* - Key
|
||||
- Required
|
||||
- Schema
|
||||
- Description
|
||||
* - connection_info
|
||||
- true
|
||||
- MsSQLSourceConnectionInfo_
|
||||
- Connection parameters for the source
|
||||
|
||||
|
||||
.. _BigQueryConfiguration:
|
||||
|
||||
BigQueryConfiguration
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. list-table::
|
||||
:header-rows: 1
|
||||
|
||||
* - Key
|
||||
- Required
|
||||
- Schema
|
||||
- Description
|
||||
* - service_account
|
||||
- true
|
||||
- ``JSON String`` | ``JSON`` | FromEnv_
|
||||
- Service account for BigQuery database
|
||||
* - project_id
|
||||
- true
|
||||
- ``String`` | FromEnv_
|
||||
- Project Id for BigQuery database
|
||||
* - datasets
|
||||
- true
|
||||
- ``[String]`` | FromEnv_
|
||||
- List of BigQuery datasets
|
||||
|
||||
|
||||
.. _PGSourceConnectionInfo:
|
||||
|
||||
PGSourceConnectionInfo
|
||||
@ -122,7 +166,7 @@ PGSourceConnectionInfo
|
||||
- ``String`` | FromEnv_
|
||||
- The database connection URL string, or as an environment variable
|
||||
* - pool_settings
|
||||
- true
|
||||
- false
|
||||
- PGPoolSettings_
|
||||
- Connection pool settings
|
||||
* - use_prepared_statements
|
||||
@ -136,6 +180,28 @@ PGSourceConnectionInfo
|
||||
- The transaction isolation level in which the queries made to the source will be run with (default: ``read-committed``).
|
||||
|
||||
|
||||
.. _MsSQLSourceConnectionInfo:
|
||||
|
||||
MsSQLSourceConnectionInfo
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. list-table::
|
||||
:header-rows: 1
|
||||
|
||||
* - Key
|
||||
- Required
|
||||
- Schema
|
||||
- Description
|
||||
* - connection_string
|
||||
- true
|
||||
- ``String`` | FromEnv_
|
||||
- The database connection string, or as an environment variable
|
||||
* - pool_settings
|
||||
- false
|
||||
- MsSQLPoolSettings_
|
||||
- Connection pool settings
|
||||
|
||||
|
||||
.. _FromEnv:
|
||||
|
||||
FromEnv
|
||||
@ -189,6 +255,28 @@ PGPoolSettings
|
||||
passed, memory from large query results may not be reclaimed. (default: 600 sec)
|
||||
|
||||
|
||||
.. _MsSQLPoolSettings:
|
||||
|
||||
MsSQLPoolSettings
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. list-table::
|
||||
:header-rows: 1
|
||||
|
||||
* - Key
|
||||
- Required
|
||||
- Schema
|
||||
- Description
|
||||
* - max_connections
|
||||
- false
|
||||
- ``Integer``
|
||||
- Maximum number of connections to be kept in the pool (default: 50)
|
||||
* - idle_timeout
|
||||
- false
|
||||
- ``Integer``
|
||||
- The idle timeout (in seconds) per connection (default: 180)
|
||||
|
||||
|
||||
.. _PGColumnType:
|
||||
|
||||
PGColumnType
|
||||
|
Loading…
Reference in New Issue
Block a user