mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-14 08:02:15 +03:00
console: better handling for unavailable GDC sources on connect and form pages
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/9139 Co-authored-by: Vijay Prasanna <11921040+vijayprasanna13@users.noreply.github.com> GitOrigin-RevId: 7fb0aeb07d13298bd420e25ca6b0408bdad9a029
This commit is contained in:
parent
06276b0055
commit
36b739c57f
@ -10,7 +10,7 @@ export default {
|
||||
component: ConnectDatabaseV2,
|
||||
decorators: [ReactQueryDecorator()],
|
||||
parameters: {
|
||||
msw: handlers({ dcAgentsAdded: true }),
|
||||
msw: handlers({ agentTestType: 'super_connector_agents_added' }),
|
||||
},
|
||||
} as ComponentMeta<typeof ConnectDatabaseV2>;
|
||||
|
||||
@ -51,7 +51,7 @@ export const FromEnvironment: ComponentStory<typeof ConnectDatabaseV2> = () => {
|
||||
</div>
|
||||
);
|
||||
};
|
||||
FromEnvironment.storyName = '💠 Using Environment (DC Agents Available)';
|
||||
FromEnvironment.storyName = '💠 Using Environment (DC Agents Added)';
|
||||
|
||||
/**
|
||||
*
|
||||
@ -65,9 +65,9 @@ FromEnvironment.storyName = '💠 Using Environment (DC Agents Available)';
|
||||
*/
|
||||
|
||||
export const FromEnvironment2 = FromEnvironment.bind({});
|
||||
FromEnvironment2.storyName = '💠 Using Environment (DC Agents NOT Available)';
|
||||
FromEnvironment2.storyName = '💠 Using Environment (DC Agents NOT Added)';
|
||||
FromEnvironment2.parameters = {
|
||||
msw: handlers({ dcAgentsAdded: false }),
|
||||
msw: handlers({ agentTestType: 'super_connector_agents_not_added' }),
|
||||
};
|
||||
/**
|
||||
*
|
||||
@ -77,9 +77,9 @@ FromEnvironment2.parameters = {
|
||||
*
|
||||
*/
|
||||
export const Playground = Template.bind({});
|
||||
Playground.storyName = '💠 Playground (DC Agents NOT Available)';
|
||||
Playground.storyName = '💠 Playground (DC Agents NOT Added)';
|
||||
Playground.parameters = {
|
||||
msw: handlers({ dcAgentsAdded: false }),
|
||||
msw: handlers({ agentTestType: 'super_connector_agents_not_added' }),
|
||||
};
|
||||
Playground.args = Template.args;
|
||||
|
||||
@ -94,9 +94,28 @@ Playground.args = Template.args;
|
||||
*/
|
||||
|
||||
export const Playground2 = Template.bind({});
|
||||
Playground2.storyName = '💠 Playground (DC Agents Available)';
|
||||
Playground2.storyName = '💠 Playground (DC Agents Added)';
|
||||
Playground2.args = Template.args;
|
||||
|
||||
/**
|
||||
*
|
||||
* Playground 3
|
||||
*
|
||||
*
|
||||
* Mock DC Agents are added in this version
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
export const Playground3 = Template.bind({});
|
||||
Playground3.storyName = '💠 Playground (DC Agents Added but not available)';
|
||||
Playground3.parameters = {
|
||||
msw: handlers({
|
||||
agentTestType: 'super_connector_agents_added_but_unavailable',
|
||||
}),
|
||||
};
|
||||
Playground3.args = Template.args;
|
||||
|
||||
/**
|
||||
* TODO:
|
||||
*
|
||||
|
@ -1,8 +1,7 @@
|
||||
import React from 'react';
|
||||
import { DriverInfo } from '../DataSource';
|
||||
import { EELiteAccess } from '../EETrial';
|
||||
import { ConnectDatabaseWrapper, FancyRadioCards } from './components';
|
||||
import { ConnectDbBody } from './ConnectDbBody';
|
||||
import { ConnectDatabaseWrapper, FancyRadioCards } from './components';
|
||||
import { DEFAULT_DRIVER } from './constants';
|
||||
import { useDatabaseConnectDrivers } from './hooks/useConnectDatabaseDrivers';
|
||||
import { DbConnectConsoleType } from './types';
|
||||
@ -32,22 +31,33 @@ export type ConnectDatabaseProps = {
|
||||
export const ConnectDatabaseV2 = (props: ConnectDatabaseProps) => {
|
||||
const { initialDriverName, eeLicenseInfo, consoleType } = props;
|
||||
|
||||
const [selectedDriver, setSelectedDriver] =
|
||||
React.useState<DriverInfo>(DEFAULT_DRIVER);
|
||||
// const [selectedDriver, setSelectedDriver] =
|
||||
// React.useState<DriverInfo>(DEFAULT_DRIVER);
|
||||
const [selectedDriverName, setSelectedDriverName] = React.useState(
|
||||
DEFAULT_DRIVER.name
|
||||
);
|
||||
|
||||
const { cardData, allDrivers, availableDrivers } = useDatabaseConnectDrivers({
|
||||
showEnterpriseDrivers: consoleType !== 'oss',
|
||||
onFirstSuccess: () =>
|
||||
setSelectedDriver(
|
||||
currentDriver =>
|
||||
setSelectedDriverName(
|
||||
current =>
|
||||
allDrivers.find(
|
||||
d =>
|
||||
d.name === initialDriverName &&
|
||||
(d.enterprise === false || consoleType !== 'oss')
|
||||
) || currentDriver
|
||||
)?.name || current
|
||||
),
|
||||
});
|
||||
|
||||
// this needs to be a reactive value hence the useMemo usage.
|
||||
// when "allDrivers" changes due to a react query invalidation/metadata reload, the properties of the driver may change
|
||||
// in order for this to reflect automatically, we make this value dependant on both the state of "allDrivers" array and the "selectedDriverName" string
|
||||
const selectedDriver = React.useMemo(
|
||||
() => allDrivers.find(d => d.name === selectedDriverName) || DEFAULT_DRIVER,
|
||||
[allDrivers, selectedDriverName]
|
||||
);
|
||||
|
||||
const isDriverAvailable = (availableDrivers ?? []).some(
|
||||
d => d.name === selectedDriver.name
|
||||
);
|
||||
@ -58,9 +68,7 @@ export const ConnectDatabaseV2 = (props: ConnectDatabaseProps) => {
|
||||
items={cardData}
|
||||
value={selectedDriver?.name}
|
||||
onChange={val => {
|
||||
setSelectedDriver(
|
||||
prev => allDrivers?.find(d => d.name === val) || prev
|
||||
);
|
||||
setSelectedDriverName(val);
|
||||
}}
|
||||
/>
|
||||
<ConnectDbBody
|
||||
|
@ -4,7 +4,6 @@ import { useAppDispatch } from '../../../../storeHooks';
|
||||
import { DriverInfo } from '../../../DataSource';
|
||||
import { useMetadata } from '../../../hasura-metadata-api';
|
||||
import { ConnectButton } from '../../components/ConnectButton';
|
||||
import { DEFAULT_DRIVER } from '../../constants';
|
||||
|
||||
export const Cloud = ({
|
||||
selectedDriver,
|
||||
@ -19,8 +18,6 @@ export const Cloud = ({
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const selectedDriverName = selectedDriver?.name ?? DEFAULT_DRIVER.name;
|
||||
|
||||
return (
|
||||
<>
|
||||
{selectedDriver?.name === 'postgres' && (
|
||||
@ -45,7 +42,7 @@ export const Cloud = ({
|
||||
</IndicatorCard>
|
||||
</div>
|
||||
) : (
|
||||
<ConnectButton driverName={selectedDriverName} />
|
||||
<ConnectButton selectedDriver={selectedDriver} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
@ -2,5 +2,5 @@ import { DriverInfo } from '../../../DataSource';
|
||||
import { ConnectButton } from '../../components/ConnectButton';
|
||||
|
||||
export const Oss = ({ selectedDriver }: { selectedDriver: DriverInfo }) => (
|
||||
<ConnectButton driverName={selectedDriver?.name} />
|
||||
<ConnectButton selectedDriver={selectedDriver} />
|
||||
);
|
||||
|
@ -12,7 +12,7 @@ export const Pro = ({
|
||||
}) => {
|
||||
const pushRoute = usePushRoute();
|
||||
return isDriverAvailable ? (
|
||||
<ConnectButton driverName={selectedDriver?.name} />
|
||||
<ConnectButton selectedDriver={selectedDriver} />
|
||||
) : (
|
||||
<div className="mt-3" data-testid="setup-connector">
|
||||
<SetupConnector
|
||||
|
@ -78,7 +78,7 @@ export const ProLite = ({
|
||||
|
||||
{(!selectedDriver?.enterprise ||
|
||||
(eeLicenseInfo === 'active' && isDriverAvailable)) && (
|
||||
<ConnectButton driverName={selectedDriver?.name} />
|
||||
<ConnectButton selectedDriver={selectedDriver} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
@ -1,17 +1,81 @@
|
||||
import React from 'react';
|
||||
import { useQueryClient } from 'react-query';
|
||||
import { useHasuraAlert } from '../../../new-components/Alert';
|
||||
import { Button } from '../../../new-components/Button';
|
||||
import { hasuraToast } from '../../../new-components/Toasts';
|
||||
import { useReloadMetadata } from '../../hasura-metadata-api/useReloadMetadata';
|
||||
import { usePushRoute } from '../hooks';
|
||||
import { DriverInfo } from '../../DataSource';
|
||||
import to from 'await-to-js';
|
||||
|
||||
export const ConnectButton = ({ driverName }: { driverName: string }) => {
|
||||
export const ConnectButton = ({
|
||||
selectedDriver,
|
||||
}: {
|
||||
selectedDriver: DriverInfo;
|
||||
}) => {
|
||||
const pushRoute = usePushRoute();
|
||||
const { hasuraConfirm } = useHasuraAlert();
|
||||
const { reloadMetadata } = useReloadMetadata();
|
||||
const client = useQueryClient();
|
||||
|
||||
const connectionIssue = !selectedDriver.available;
|
||||
|
||||
const handleClick = () => {
|
||||
if (connectionIssue) {
|
||||
hasuraConfirm({
|
||||
message: (
|
||||
<>
|
||||
<p>The selected driver cannot be reached at the moment.</p>
|
||||
<p>
|
||||
This is usually due to a connection issue and can be resolved by
|
||||
reloading metadata.
|
||||
</p>
|
||||
<p>If this issue persists, please contact support.</p>
|
||||
</>
|
||||
),
|
||||
title: 'Driver Error',
|
||||
confirmText: 'Reload Metadata',
|
||||
|
||||
onCloseAsync: async ({ confirmed }) => {
|
||||
if (!confirmed) return;
|
||||
|
||||
const [err, result] = await to(
|
||||
reloadMetadata({
|
||||
shouldReloadAllSources: false,
|
||||
shouldReloadRemoteSchemas: false,
|
||||
})
|
||||
);
|
||||
if (err) {
|
||||
hasuraToast({
|
||||
message: 'There was an error reloading your metadata.',
|
||||
title: 'Error',
|
||||
type: 'error',
|
||||
});
|
||||
return;
|
||||
}
|
||||
const { success } = result;
|
||||
|
||||
if (success) {
|
||||
client.invalidateQueries();
|
||||
return { withSuccess: true, successText: 'Metadata Reloaded' };
|
||||
} else {
|
||||
hasuraToast({
|
||||
message: 'There was an error reloading your metadata.',
|
||||
title: 'Error',
|
||||
type: 'error',
|
||||
});
|
||||
return;
|
||||
}
|
||||
},
|
||||
});
|
||||
} else {
|
||||
pushRoute(`/data/v2/manage/database/add?driver=${selectedDriver.name}`);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<Button
|
||||
className="mt-6 self-end"
|
||||
data-testid="connect-existing-button"
|
||||
onClick={() =>
|
||||
pushRoute(`/data/v2/manage/database/add?driver=${driverName}`)
|
||||
}
|
||||
onClick={handleClick}
|
||||
>
|
||||
Connect Existing Database
|
||||
</Button>
|
||||
|
@ -7,7 +7,7 @@ export default {
|
||||
component: ConnectGDCSourceWidget,
|
||||
decorators: [ReactQueryDecorator()],
|
||||
parameters: {
|
||||
msw: handlers(),
|
||||
msw: handlers({ agentTestType: 'super_connector_agents_not_added' }),
|
||||
},
|
||||
} as ComponentMeta<typeof ConnectGDCSourceWidget>;
|
||||
|
||||
|
@ -1,25 +1,29 @@
|
||||
import { DataSource, Feature } from '../../../DataSource';
|
||||
import { useHttpClient } from '../../../Network';
|
||||
import { OpenApi3Form } from '../../../OpenApi3Form';
|
||||
import { Button } from '../../../../new-components/Button';
|
||||
import { transformSchemaToZodObject } from '../../../OpenApi3Form/utils';
|
||||
import { InputField, useConsoleForm } from '../../../../new-components/Form';
|
||||
import { Tabs } from '../../../../new-components/Tabs';
|
||||
import to from 'await-to-js';
|
||||
import { AxiosError } from 'axios';
|
||||
import get from 'lodash/get';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { FaExclamationTriangle } from 'react-icons/fa';
|
||||
import Skeleton from 'react-loading-skeleton';
|
||||
import { useQuery } from 'react-query';
|
||||
import { z, ZodSchema } from 'zod';
|
||||
import { graphQLCustomizationSchema } from '../GraphQLCustomization/schema';
|
||||
import { GraphQLCustomization } from '../GraphQLCustomization/GraphQLCustomization';
|
||||
import { ZodSchema, z } from 'zod';
|
||||
import { Button } from '../../../../new-components/Button';
|
||||
import { Collapsible } from '../../../../new-components/Collapsible';
|
||||
import { InputField, useConsoleForm } from '../../../../new-components/Form';
|
||||
import { IndicatorCard } from '../../../../new-components/IndicatorCard';
|
||||
import { Tabs } from '../../../../new-components/Tabs';
|
||||
import { hasuraToast } from '../../../../new-components/Toasts';
|
||||
import { useAvailableDrivers } from '../../../ConnectDB/hooks';
|
||||
import { DataSource, Feature } from '../../../DataSource';
|
||||
import { useHttpClient } from '../../../Network';
|
||||
import { OpenApi3Form } from '../../../OpenApi3Form';
|
||||
import { transformSchemaToZodObject } from '../../../OpenApi3Form/utils';
|
||||
import { useMetadata } from '../../../hasura-metadata-api';
|
||||
import { useManageDatabaseConnection } from '../../hooks/useManageDatabaseConnection';
|
||||
import { DisplayToastErrorMessage } from '../Common/DisplayToastErrorMessage';
|
||||
import { GraphQLCustomization } from '../GraphQLCustomization/GraphQLCustomization';
|
||||
import { graphQLCustomizationSchema } from '../GraphQLCustomization/schema';
|
||||
import { adaptGraphQLCustomization } from '../GraphQLCustomization/utils/adaptResponse';
|
||||
import { generateGDCRequestPayload } from './utils/generateRequest';
|
||||
import { hasuraToast } from '../../../../new-components/Toasts';
|
||||
import { useManageDatabaseConnection } from '../../hooks/useManageDatabaseConnection';
|
||||
import { Collapsible } from '../../../../new-components/Collapsible';
|
||||
import { DisplayToastErrorMessage } from '../Common/DisplayToastErrorMessage';
|
||||
import { useAvailableDrivers } from '../../../ConnectDB/hooks';
|
||||
|
||||
interface ConnectGDCSourceWidgetProps {
|
||||
driver: string;
|
||||
@ -31,9 +35,13 @@ const useFormValidationSchema = (driver: string) => {
|
||||
return useQuery({
|
||||
queryKey: ['form-schema', driver],
|
||||
queryFn: async () => {
|
||||
const configSchemas = await DataSource(
|
||||
httpClient
|
||||
).connectDB.getConfigSchema(driver);
|
||||
const [err, configSchemas] = await to(
|
||||
DataSource(httpClient).connectDB.getConfigSchema(driver)
|
||||
);
|
||||
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
if (!configSchemas || configSchemas === Feature.NotImplemented)
|
||||
throw Error('Could not retrive config schema info for driver');
|
||||
@ -57,36 +65,57 @@ export const ConnectGDCSourceWidget = (props: ConnectGDCSourceWidgetProps) => {
|
||||
const { driver, dataSourceName } = props;
|
||||
const [tab, setTab] = useState('connection_details');
|
||||
|
||||
const { data: drivers } = useAvailableDrivers();
|
||||
const {
|
||||
data: drivers,
|
||||
isLoading: isLoadingAvailableDrivers,
|
||||
isError: isAvailableDriversError,
|
||||
error: availableDriversError,
|
||||
} = useAvailableDrivers();
|
||||
const driverDisplayName =
|
||||
drivers?.find(d => d.name === driver)?.displayName ?? driver;
|
||||
|
||||
const { data: metadataSource } = useMetadata(m =>
|
||||
const {
|
||||
data: metadataSource,
|
||||
isLoading: isLoadingMetadata,
|
||||
isError: isMetadataError,
|
||||
error: metadataError,
|
||||
} = useMetadata(m =>
|
||||
m.metadata.sources.find(source => source.name === dataSourceName)
|
||||
);
|
||||
|
||||
const { createConnection, editConnection, isLoading } =
|
||||
useManageDatabaseConnection({
|
||||
onSuccess: () => {
|
||||
hasuraToast({
|
||||
type: 'success',
|
||||
title: isEditMode
|
||||
? 'Database updated successfully!'
|
||||
: 'Database added successfully!',
|
||||
});
|
||||
},
|
||||
onError: err => {
|
||||
hasuraToast({
|
||||
type: 'error',
|
||||
title: err.name,
|
||||
children: <DisplayToastErrorMessage message={err.message} />,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const isEditMode = !!dataSourceName;
|
||||
const {
|
||||
createConnection,
|
||||
editConnection,
|
||||
isLoading: isLoadingCreateConnection,
|
||||
} = useManageDatabaseConnection({
|
||||
onSuccess: () => {
|
||||
hasuraToast({
|
||||
type: 'success',
|
||||
title: isEditMode
|
||||
? 'Database updated successfully!'
|
||||
: 'Database added successfully!',
|
||||
});
|
||||
},
|
||||
onError: err => {
|
||||
hasuraToast({
|
||||
type: 'error',
|
||||
title: err.name,
|
||||
children: <DisplayToastErrorMessage message={err.message} />,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const { data } = useFormValidationSchema(driver);
|
||||
const {
|
||||
data,
|
||||
isLoading: isLoadingValidationSchema,
|
||||
isError: isValidationSchemaError,
|
||||
error: validationSchemaError,
|
||||
} = useFormValidationSchema(driver);
|
||||
|
||||
const isLoading =
|
||||
(isLoadingMetadata && !isMetadataError) ||
|
||||
(isLoadingValidationSchema && !isValidationSchemaError) ||
|
||||
(isLoadingAvailableDrivers && !isAvailableDriversError);
|
||||
|
||||
const [schema, setSchema] = useState<ZodSchema>(z.any());
|
||||
|
||||
@ -113,7 +142,52 @@ export const ConnectGDCSourceWidget = (props: ConnectGDCSourceWidgetProps) => {
|
||||
});
|
||||
}, [metadataSource, reset]);
|
||||
|
||||
if (!data?.configSchemas) return null;
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div>
|
||||
<Skeleton count={10} height={30} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (validationSchemaError) {
|
||||
const err = validationSchemaError as AxiosError<{ error?: string }>;
|
||||
return (
|
||||
<IndicatorCard status="negative">
|
||||
{err?.response?.data?.error ||
|
||||
err.toString() ||
|
||||
'An error occurred loading the connection configuration.'}
|
||||
</IndicatorCard>
|
||||
);
|
||||
}
|
||||
if (metadataError) {
|
||||
const err = metadataError as AxiosError<{ error?: string }>;
|
||||
return (
|
||||
<IndicatorCard status="negative">
|
||||
{err?.response?.data?.error ||
|
||||
err.toString() ||
|
||||
'An error occurred loading metadata.'}
|
||||
</IndicatorCard>
|
||||
);
|
||||
}
|
||||
if (availableDriversError) {
|
||||
const err = availableDriversError as AxiosError<{ error?: string }>;
|
||||
return (
|
||||
<IndicatorCard status="negative">
|
||||
{err?.response?.data?.error ||
|
||||
err.toString() ||
|
||||
'An error occurred loading the available drivers.'}
|
||||
</IndicatorCard>
|
||||
);
|
||||
}
|
||||
|
||||
if (!data?.configSchemas) {
|
||||
return (
|
||||
<IndicatorCard status="negative">
|
||||
An error occurred loading the connection configuration.
|
||||
</IndicatorCard>
|
||||
);
|
||||
}
|
||||
|
||||
const handleSubmit = (formValues: any) => {
|
||||
const payload = generateGDCRequestPayload({
|
||||
@ -133,8 +207,6 @@ export const ConnectGDCSourceWidget = (props: ConnectGDCSourceWidgetProps) => {
|
||||
get(formState.errors, 'configuration.connectionInfo'),
|
||||
].filter(Boolean);
|
||||
|
||||
console.log(formState.errors);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="text-xl text-gray-600 font-semibold">
|
||||
@ -186,7 +258,7 @@ export const ConnectGDCSourceWidget = (props: ConnectGDCSourceWidgetProps) => {
|
||||
<Button
|
||||
type="submit"
|
||||
mode="primary"
|
||||
isLoading={isLoading}
|
||||
isLoading={isLoadingCreateConnection}
|
||||
loadingText="Saving"
|
||||
>
|
||||
{isEditMode ? 'Update Connection' : 'Connect Database'}
|
||||
|
@ -5,7 +5,7 @@ import { ConnectGDCSourceWidget } from '../ConnectGDCSourceWidget/ConnectGDCSour
|
||||
import { ConnectMssqlWidget } from '../ConnectMssqlWidget/ConnectMssqlWidget';
|
||||
import { ConnectPostgresWidget } from '../ConnectPostgresWidget/ConnectPostgresWidget';
|
||||
|
||||
const getEditDatasourceName = (): string | undefined => {
|
||||
const getDataSourceNameFromUrlParams = (): string | undefined => {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
|
||||
const database = urlParams.get('database');
|
||||
@ -13,7 +13,7 @@ const getEditDatasourceName = (): string | undefined => {
|
||||
return database ?? undefined;
|
||||
};
|
||||
|
||||
const getDriverName = (): string | undefined => {
|
||||
const getDriverNameFromUrlParams = (): string | undefined => {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
|
||||
const driver = urlParams.get('driver');
|
||||
@ -22,8 +22,8 @@ const getDriverName = (): string | undefined => {
|
||||
};
|
||||
|
||||
const ConnectDatabaseWrapper = () => {
|
||||
const dataSourceName = getEditDatasourceName();
|
||||
const driver = getDriverName();
|
||||
const dataSourceName = getDataSourceNameFromUrlParams();
|
||||
const driver = getDriverNameFromUrlParams();
|
||||
|
||||
if (!driver) return <div>Error. No driver found.</div>;
|
||||
|
||||
@ -68,7 +68,7 @@ const ConnectDatabaseWrapper = () => {
|
||||
};
|
||||
|
||||
export const ConnectUIContainer = () => {
|
||||
const driver = getDriverName();
|
||||
const driver = getDriverNameFromUrlParams();
|
||||
return (
|
||||
<div className="p-4">
|
||||
<BreadCrumb
|
||||
|
@ -1,11 +1,14 @@
|
||||
import React from 'react';
|
||||
// import { MdSignalWifiStatusbarConnectedNoInternet1 } from 'react-icons/md';
|
||||
import { Badge } from '../../../new-components/Badge';
|
||||
import { IoCloudOfflineOutline } from 'react-icons/io5';
|
||||
|
||||
export const DatabaseLogo: React.FC<{
|
||||
title: string;
|
||||
image: string;
|
||||
releaseName?: string;
|
||||
}> = ({ title, image, releaseName }) => {
|
||||
noConnection: boolean;
|
||||
}> = ({ title, image, releaseName, noConnection }) => {
|
||||
return (
|
||||
// adding pointer evens none just to make sure none of this captures clicks since that's handled in the parent for the radio buttons
|
||||
<div className="flex flex-col mt-2 items-center justify-center absolute h-full w-full pointer-events-none">
|
||||
@ -15,10 +18,18 @@ export const DatabaseLogo: React.FC<{
|
||||
alt={`${title} logo`}
|
||||
/>
|
||||
<div className="text-black text-base">{title}</div>
|
||||
{releaseName && releaseName !== 'GA' && (
|
||||
<div className="absolute top-0 right-0 m-1 scale-75">
|
||||
<Badge color="indigo">{releaseName}</Badge>
|
||||
|
||||
{noConnection ? (
|
||||
<div className="absolute top-0 right-0 m-3 ">
|
||||
<IoCloudOfflineOutline size={20} className="text-red-500" />
|
||||
</div>
|
||||
) : (
|
||||
releaseName &&
|
||||
releaseName !== 'GA' && (
|
||||
<div className="absolute top-0 right-0 m-1 scale-75">
|
||||
<Badge color="indigo">{releaseName}</Badge>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
@ -3,12 +3,22 @@ import { useAvailableDrivers } from '../../ConnectDB/hooks';
|
||||
import { DriverInfo } from '../../DataSource';
|
||||
import { DatabaseLogo } from '../components';
|
||||
import dbLogos from '../graphics/db-logos';
|
||||
import { SuperConnectorDrivers as SuperDrivers } from '../../hasura-metadata-types';
|
||||
|
||||
type useDatabaseConnectDriversProps = {
|
||||
onFirstSuccess?: (data: DriverInfo[]) => void;
|
||||
showEnterpriseDrivers?: boolean;
|
||||
};
|
||||
|
||||
export const kindNameMap: Record<SuperDrivers, string> = {
|
||||
sqlite: 'Hasura SQLite',
|
||||
athena: 'Amazon Athena',
|
||||
snowflake: 'Snowflake',
|
||||
mysql8: 'MySql',
|
||||
mariadb: 'MariaDB',
|
||||
oracle: 'Oracle',
|
||||
};
|
||||
|
||||
// a GDC driver is only "available" once an agent is added for it
|
||||
// these are drivers are a special case bc we may want to display them in the UI before their agent's are added in certain cases
|
||||
const SuperConnectorDrivers: readonly DriverInfo[] = [
|
||||
@ -61,10 +71,15 @@ export const useDatabaseConnectDrivers = ({
|
||||
showEnterpriseDrivers = true,
|
||||
onFirstSuccess,
|
||||
}: useDatabaseConnectDriversProps = {}) => {
|
||||
const { data: availableDrivers } = useAvailableDrivers({
|
||||
const { data } = useAvailableDrivers({
|
||||
onFirstSuccess,
|
||||
});
|
||||
|
||||
const availableDrivers = data?.map(d => ({
|
||||
...d,
|
||||
displayName: d.displayName || kindNameMap[d.name] || d.name,
|
||||
}));
|
||||
|
||||
const allDrivers = sortBy(
|
||||
uniqBy(
|
||||
[...(availableDrivers ?? []), ...SuperConnectorDrivers],
|
||||
@ -80,6 +95,7 @@ export const useDatabaseConnectDrivers = ({
|
||||
content: (
|
||||
<DatabaseLogo
|
||||
title={d.displayName}
|
||||
noConnection={d.available === false}
|
||||
image={dbLogos[d.name] || dbLogos.default}
|
||||
releaseName={d.release}
|
||||
/>
|
||||
|
@ -255,6 +255,67 @@ export const mockSourceKinds = {
|
||||
},
|
||||
],
|
||||
},
|
||||
agentsAddedSuperConnectorNotAvailable: {
|
||||
sources: [
|
||||
{
|
||||
available: true,
|
||||
builtin: true,
|
||||
display_name: 'pg',
|
||||
kind: 'pg',
|
||||
},
|
||||
{
|
||||
available: true,
|
||||
builtin: true,
|
||||
display_name: 'citus',
|
||||
kind: 'citus',
|
||||
},
|
||||
{
|
||||
available: true,
|
||||
builtin: true,
|
||||
display_name: 'cockroach',
|
||||
kind: 'cockroach',
|
||||
},
|
||||
{
|
||||
available: true,
|
||||
builtin: true,
|
||||
display_name: 'mssql',
|
||||
kind: 'mssql',
|
||||
},
|
||||
{
|
||||
available: true,
|
||||
builtin: true,
|
||||
display_name: 'bigquery',
|
||||
kind: 'bigquery',
|
||||
},
|
||||
{
|
||||
available: false,
|
||||
builtin: false,
|
||||
//display_name: 'Hasura SQLite',
|
||||
kind: 'sqlite',
|
||||
},
|
||||
{
|
||||
available: false,
|
||||
builtin: false,
|
||||
//display_name: 'Amazon Athena',
|
||||
kind: 'athena',
|
||||
release_name: 'Beta',
|
||||
},
|
||||
{
|
||||
available: false,
|
||||
builtin: false,
|
||||
//display_name: 'Snowflake',
|
||||
kind: 'snowflake',
|
||||
release_name: 'Beta',
|
||||
},
|
||||
{
|
||||
available: false,
|
||||
builtin: false,
|
||||
//display_name: 'MySQL',
|
||||
kind: 'mysql8',
|
||||
release_name: 'Alpha',
|
||||
},
|
||||
],
|
||||
},
|
||||
agentsNotAdded: {
|
||||
sources: [
|
||||
{
|
||||
@ -287,12 +348,6 @@ export const mockSourceKinds = {
|
||||
display_name: 'bigquery',
|
||||
kind: 'bigquery',
|
||||
},
|
||||
{
|
||||
available: true,
|
||||
builtin: true,
|
||||
display_name: 'MySQL',
|
||||
kind: 'mysql8',
|
||||
},
|
||||
{
|
||||
available: true,
|
||||
builtin: false,
|
||||
|
@ -4,20 +4,27 @@ import {
|
||||
mockMetadata,
|
||||
mockSourceKinds,
|
||||
} from './data.mock';
|
||||
type AgentTestType =
|
||||
| 'super_connector_agents_added'
|
||||
| 'super_connector_agents_not_added'
|
||||
| 'super_connector_agents_added_but_unavailable';
|
||||
|
||||
export const handlers = ({
|
||||
dcAgentsAdded,
|
||||
}: { dcAgentsAdded?: boolean } = {}) => [
|
||||
export const handlers = (props?: { agentTestType: AgentTestType }) => [
|
||||
rest.post('http://localhost:8080/v1/metadata', (req, res, ctx) => {
|
||||
const requestBody = req.body as Record<string, any>;
|
||||
if (requestBody.type === 'list_source_kinds') {
|
||||
return res(
|
||||
ctx.json(
|
||||
dcAgentsAdded
|
||||
? mockSourceKinds.agentsAdded
|
||||
: mockSourceKinds.agentsNotAdded
|
||||
)
|
||||
);
|
||||
switch (props?.agentTestType) {
|
||||
case 'super_connector_agents_added':
|
||||
return res(ctx.json(mockSourceKinds.agentsAdded));
|
||||
case 'super_connector_agents_not_added':
|
||||
return res(ctx.json(mockSourceKinds.agentsNotAdded));
|
||||
case 'super_connector_agents_added_but_unavailable':
|
||||
return res(
|
||||
ctx.json(mockSourceKinds.agentsAddedSuperConnectorNotAvailable)
|
||||
);
|
||||
default:
|
||||
return res(ctx.json(mockSourceKinds.agentsNotAdded));
|
||||
}
|
||||
}
|
||||
if (requestBody.type === 'export_metadata')
|
||||
return res(ctx.json(mockMetadata));
|
||||
|
@ -0,0 +1,38 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { reloadMetadata } from '../../metadata/actions';
|
||||
|
||||
export const useReloadMetadata = () => {
|
||||
const dispatch = useDispatch();
|
||||
const reload = useCallback(
|
||||
({
|
||||
shouldReloadAllSources = false,
|
||||
shouldReloadRemoteSchemas = false,
|
||||
}: {
|
||||
shouldReloadRemoteSchemas: boolean;
|
||||
shouldReloadAllSources: boolean;
|
||||
}) => {
|
||||
return new Promise<{ success: boolean }>((resolve, reject) => {
|
||||
try {
|
||||
dispatch(
|
||||
reloadMetadata(
|
||||
shouldReloadRemoteSchemas,
|
||||
shouldReloadAllSources,
|
||||
() => {
|
||||
resolve({ success: true });
|
||||
},
|
||||
() => {
|
||||
resolve({ success: false });
|
||||
}
|
||||
)
|
||||
);
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
return { reloadMetadata: reload };
|
||||
};
|
Loading…
Reference in New Issue
Block a user