mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-14 08:02:15 +03:00
console: Add a confirm dialog when removing a data source
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/8728 GitOrigin-RevId: 1f90c60803dcddc78ef1af0618d2153214d7917a
This commit is contained in:
parent
cd024dcaa4
commit
27a02cf2bc
@ -1,69 +1,32 @@
|
||||
import { CardedTable } from '../../../../new-components/CardedTable';
|
||||
import { Button } from '../../../../new-components/Button';
|
||||
import {
|
||||
FaCheck,
|
||||
FaEdit,
|
||||
FaExclamationTriangle,
|
||||
FaMinusCircle,
|
||||
FaTrash,
|
||||
FaUndo,
|
||||
FaRedoAlt,
|
||||
FaExternalLinkAlt,
|
||||
} from 'react-icons/fa';
|
||||
import { useMetadata } from '../../../MetadataAPI';
|
||||
import _push from '../../../../components/Services/Data/push';
|
||||
import { useReloadSource } from '../../hooks/useReloadSource';
|
||||
import { useDropSource } from '../../hooks/useDropSource';
|
||||
import { IndicatorCard } from '../../../../new-components/IndicatorCard';
|
||||
import Skeleton from 'react-loading-skeleton';
|
||||
import { useInconsistentSources } from '../../hooks/useInconsistentSources';
|
||||
import { Details } from './parts/Details';
|
||||
import { useState } from 'react';
|
||||
import { useDatabaseVersion } from '../../hooks/useDatabaseVersion';
|
||||
import { useDatabaseLatencyCheck } from '../../hooks/useDatabaseLatencyCheck';
|
||||
import to from 'await-to-js';
|
||||
import React, { useState } from 'react';
|
||||
import { BiTimer } from 'react-icons/bi';
|
||||
import { hasuraToast } from '../../../../new-components/Toasts';
|
||||
import { Latency } from '../../types';
|
||||
import { Badge } from '../../../../new-components/Badge';
|
||||
import { LearnMoreLink } from '../../../../new-components/LearnMoreLink';
|
||||
import { getProjectId, isCloudConsole } from '../../../../utils/cloudConsole';
|
||||
import { FaEdit, FaTrash, FaUndo } from 'react-icons/fa';
|
||||
import Skeleton from 'react-loading-skeleton';
|
||||
import globals from '../../../../Globals';
|
||||
import { useUpdateProjectRegion } from '../../hooks/useUpdateProjectRegion';
|
||||
import { useAppDispatch } from '../../../../storeHooks';
|
||||
import _push from '../../../../components/Services/Data/push';
|
||||
import { exportMetadata } from '../../../../metadata/actions';
|
||||
import { MetadataDataSource } from '../../../../metadata/types';
|
||||
import { useDestructiveAlert } from '../../../../new-components/Alert';
|
||||
import { Button } from '../../../../new-components/Button';
|
||||
import { CardedTable } from '../../../../new-components/CardedTable';
|
||||
import { IndicatorCard } from '../../../../new-components/IndicatorCard';
|
||||
import { hasuraToast } from '../../../../new-components/Toasts';
|
||||
import { useAppDispatch } from '../../../../storeHooks';
|
||||
import { getProjectId, isCloudConsole } from '../../../../utils/cloudConsole';
|
||||
import { useMetadata } from '../../../MetadataAPI';
|
||||
import { useDatabaseLatencyCheck } from '../../hooks/useDatabaseLatencyCheck';
|
||||
import { useDatabaseVersion } from '../../hooks/useDatabaseVersion';
|
||||
import { useDropSource } from '../../hooks/useDropSource';
|
||||
import { useInconsistentSources } from '../../hooks/useInconsistentSources';
|
||||
import { useReloadSource } from '../../hooks/useReloadSource';
|
||||
import { useUpdateProjectRegion } from '../../hooks/useUpdateProjectRegion';
|
||||
import { Latency } from '../../types';
|
||||
import { AccelerateProject, Details, LatencyBadge } from './parts';
|
||||
|
||||
const LatencyBadge = ({
|
||||
latencies,
|
||||
dataSourceName,
|
||||
}: {
|
||||
latencies: Latency[];
|
||||
dataSourceName: string;
|
||||
}) => {
|
||||
const currentDataSourceLatencyInfo = latencies.find(
|
||||
latencyInfo => latencyInfo.dataSourceName === dataSourceName
|
||||
);
|
||||
|
||||
if (!currentDataSourceLatencyInfo) return null;
|
||||
|
||||
if (currentDataSourceLatencyInfo.avgLatency < 100)
|
||||
return (
|
||||
<Badge color="green">
|
||||
<FaCheck className="mr-xs" /> Connection
|
||||
</Badge>
|
||||
);
|
||||
|
||||
if (currentDataSourceLatencyInfo.avgLatency < 200)
|
||||
return (
|
||||
<Badge color="yellow">
|
||||
<FaMinusCircle className="mr-xs" /> Acceptable
|
||||
</Badge>
|
||||
);
|
||||
|
||||
return (
|
||||
<Badge color="red">
|
||||
<FaExclamationTriangle className="mr-xs" /> Elevated Latency
|
||||
</Badge>
|
||||
);
|
||||
type DatabaseItem = {
|
||||
dataSourceName: MetadataDataSource['name'];
|
||||
driver: MetadataDataSource['kind'];
|
||||
};
|
||||
|
||||
export const ListConnectedDatabases = (props?: { className?: string }) => {
|
||||
@ -77,7 +40,6 @@ export const ListConnectedDatabases = (props?: { className?: string }) => {
|
||||
} = useDatabaseLatencyCheck({
|
||||
enabled: false,
|
||||
onSuccess: data => {
|
||||
console.log('on success', data);
|
||||
const result = (data as any).latencies as Latency[];
|
||||
const isAnyLatencyHigh = result.find(latency => latency.avgLatency > 200);
|
||||
setShowAccelerateProjectSection(!!isAnyLatencyHigh);
|
||||
@ -91,19 +53,18 @@ export const ListConnectedDatabases = (props?: { className?: string }) => {
|
||||
},
|
||||
});
|
||||
|
||||
const {
|
||||
mutate: updateProjectRegionForRowId,
|
||||
// isLoading: isUpdatingProjectRegion,
|
||||
} = useUpdateProjectRegion();
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const [activeRow, setActiveRow] = useState<number>();
|
||||
|
||||
const { reloadSource, isLoading: isSourceReloading } = useReloadSource();
|
||||
|
||||
const { dropSource, isLoading: isSourceRemovalInProgress } = useDropSource({
|
||||
onSuccess: () => {
|
||||
dispatch(exportMetadata());
|
||||
},
|
||||
});
|
||||
|
||||
const {
|
||||
data: inconsistentSources,
|
||||
isLoading: isInconsistentFetchCallLoading,
|
||||
@ -128,81 +89,124 @@ export const ListConnectedDatabases = (props?: { className?: string }) => {
|
||||
|
||||
const isCurrentRow = (rowIndex: number) => rowIndex === activeRow;
|
||||
|
||||
if (isLoading) return <>Loading...</>;
|
||||
|
||||
const columns = ['database', 'driver', '', ''];
|
||||
|
||||
const rowData = (databaseList ?? []).map((databaseItem, index) => [
|
||||
<div>{databaseItem.dataSourceName}</div>,
|
||||
databaseItem.driver,
|
||||
isDatabaseVersionLoading || isInconsistentFetchCallLoading ? (
|
||||
<Skeleton />
|
||||
) : (
|
||||
<Details
|
||||
inconsistentSources={inconsistentSources ?? []}
|
||||
details={{
|
||||
version:
|
||||
(databaseVersions ?? []).find(
|
||||
entry => entry.dataSourceName === databaseItem.dataSourceName
|
||||
)?.version ?? '',
|
||||
}}
|
||||
dataSourceName={databaseItem.dataSourceName}
|
||||
/>
|
||||
),
|
||||
<div className="flex justify-center">
|
||||
<LatencyBadge
|
||||
latencies={latencies ?? []}
|
||||
dataSourceName={databaseItem.dataSourceName}
|
||||
/>
|
||||
</div>,
|
||||
<div
|
||||
className="flex gap-4 justify-end px-4"
|
||||
onClick={e => {
|
||||
console.log('parent event captured');
|
||||
setActiveRow(index);
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
icon={<FaUndo />}
|
||||
size="sm"
|
||||
onClick={() => reloadSource(databaseItem.dataSourceName)}
|
||||
isLoading={isSourceReloading && isCurrentRow(index)}
|
||||
loadingText="Reloading"
|
||||
>
|
||||
Reload
|
||||
</Button>
|
||||
<Button
|
||||
icon={<FaEdit />}
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
dispatch(
|
||||
_push(
|
||||
`/data/v2/manage/database/edit?driver=${databaseItem.driver}&database=${databaseItem.dataSourceName}`
|
||||
)
|
||||
);
|
||||
}}
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
<Button
|
||||
icon={<FaTrash />}
|
||||
mode="destructive"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
dropSource({
|
||||
driver: databaseItem.driver,
|
||||
dataSourceName: databaseItem.dataSourceName,
|
||||
});
|
||||
}}
|
||||
isLoading={isSourceRemovalInProgress && isCurrentRow(index)}
|
||||
loadingText="Deleting"
|
||||
>
|
||||
Remove
|
||||
</Button>
|
||||
</div>,
|
||||
]);
|
||||
const handleEdit = React.useCallback((databaseItem: DatabaseItem) => {
|
||||
dispatch(
|
||||
_push(
|
||||
`/data/v2/manage/database/edit?driver=${databaseItem.driver}&database=${databaseItem.dataSourceName}`
|
||||
)
|
||||
);
|
||||
}, []);
|
||||
|
||||
const openUpdateProjectRegionPage = (_rowId?: string) => {
|
||||
const { destructivePrompt } = useDestructiveAlert();
|
||||
|
||||
const handleRemove = React.useCallback(
|
||||
(databaseItem: DatabaseItem) => {
|
||||
destructivePrompt({
|
||||
resourceName: databaseItem.dataSourceName,
|
||||
resourceType: 'Data Source',
|
||||
destroyTerm: 'remove',
|
||||
onConfirm: async () => {
|
||||
let success = true;
|
||||
|
||||
const [err] = await to(
|
||||
dropSource({
|
||||
driver: databaseItem.driver,
|
||||
dataSourceName: databaseItem.dataSourceName,
|
||||
})
|
||||
);
|
||||
|
||||
if (err) {
|
||||
success = false;
|
||||
}
|
||||
|
||||
return success;
|
||||
},
|
||||
});
|
||||
},
|
||||
[dropSource]
|
||||
);
|
||||
|
||||
const rowData = React.useMemo(
|
||||
() =>
|
||||
(databaseList ?? []).map((databaseItem, index) => [
|
||||
<div>{databaseItem.dataSourceName}</div>,
|
||||
databaseItem.driver,
|
||||
isDatabaseVersionLoading || isInconsistentFetchCallLoading ? (
|
||||
<Skeleton />
|
||||
) : (
|
||||
<Details
|
||||
inconsistentSources={inconsistentSources ?? []}
|
||||
details={{
|
||||
version:
|
||||
(databaseVersions ?? []).find(
|
||||
entry => entry.dataSourceName === databaseItem.dataSourceName
|
||||
)?.version ?? '',
|
||||
}}
|
||||
dataSourceName={databaseItem.dataSourceName}
|
||||
/>
|
||||
),
|
||||
<div className="flex justify-center">
|
||||
<LatencyBadge
|
||||
latencies={latencies ?? []}
|
||||
dataSourceName={databaseItem.dataSourceName}
|
||||
/>
|
||||
</div>,
|
||||
<div
|
||||
className="flex gap-4 justify-end px-4"
|
||||
onClick={e => {
|
||||
setActiveRow(index);
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
icon={<FaUndo />}
|
||||
size="sm"
|
||||
onClick={() => reloadSource(databaseItem.dataSourceName)}
|
||||
isLoading={isSourceReloading && isCurrentRow(index)}
|
||||
loadingText="Reloading"
|
||||
>
|
||||
Reload
|
||||
</Button>
|
||||
<Button
|
||||
icon={<FaEdit />}
|
||||
size="sm"
|
||||
onClick={() => handleEdit(databaseItem)}
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
<Button
|
||||
icon={<FaTrash />}
|
||||
mode="destructive"
|
||||
size="sm"
|
||||
onClick={() => handleRemove(databaseItem)}
|
||||
isLoading={isSourceRemovalInProgress && isCurrentRow(index)}
|
||||
loadingText="Deleting"
|
||||
>
|
||||
Remove
|
||||
</Button>
|
||||
</div>,
|
||||
]),
|
||||
[
|
||||
databaseList,
|
||||
databaseVersions,
|
||||
inconsistentSources,
|
||||
isCurrentRow,
|
||||
isDatabaseVersionLoading,
|
||||
isInconsistentFetchCallLoading,
|
||||
isSourceReloading,
|
||||
isSourceRemovalInProgress,
|
||||
latencies,
|
||||
reloadSource,
|
||||
]
|
||||
);
|
||||
|
||||
const {
|
||||
mutate: updateProjectRegionForRowId,
|
||||
// isLoading: isUpdatingProjectRegion,
|
||||
} = useUpdateProjectRegion();
|
||||
|
||||
const openUpdateProjectRegionPage = React.useCallback((_rowId?: string) => {
|
||||
if (!_rowId) {
|
||||
hasuraToast({
|
||||
type: 'error',
|
||||
@ -224,7 +228,9 @@ export const ListConnectedDatabases = (props?: { className?: string }) => {
|
||||
const cloudDetailsPage = `${window.location.protocol}//${window.location.host}/project/${projectId}/details?open_update_region_drawer=true`;
|
||||
|
||||
window.open(cloudDetailsPage, '_blank');
|
||||
};
|
||||
}, []);
|
||||
|
||||
if (isLoading) return <>Loading...</>;
|
||||
|
||||
return (
|
||||
<div className={props?.className}>
|
||||
@ -242,47 +248,15 @@ export const ListConnectedDatabases = (props?: { className?: string }) => {
|
||||
)}
|
||||
|
||||
{showAccelerateProjectSection ? (
|
||||
<div className="mt-xs">
|
||||
<IndicatorCard
|
||||
status="negative"
|
||||
headline="Accelerate your Hasura Project"
|
||||
>
|
||||
<div className="flex items-center flex-row">
|
||||
<span>
|
||||
Databases marked with “Elevated Latency” indicate that it took
|
||||
us over 200 ms for this Hasura project to communicate with your
|
||||
database. These conditions generally happen when databases and
|
||||
projects are in geographically distant regions. This can cause
|
||||
API and subsequently application performance issues. We want
|
||||
your GraphQL APIs to be <b>lightning fast</b>, therefore we
|
||||
recommend that you either deploy your Hasura project in the same
|
||||
region as your database or select a database instance
|
||||
that's closer to where you've deployed Hasura.
|
||||
<LearnMoreLink href="https://hasura.io/docs/latest/projects/regions/#changing-region-of-an-existing-project" />
|
||||
</span>
|
||||
<div className="flex items-center flex-row ml-xs">
|
||||
<Button
|
||||
className="mr-xs"
|
||||
onClick={() => {
|
||||
refetch();
|
||||
}}
|
||||
isLoading={databaseCheckLoading}
|
||||
loadingText="Measuring Latencies..."
|
||||
icon={<FaRedoAlt />}
|
||||
>
|
||||
Re-check Database Latency
|
||||
</Button>
|
||||
<Button
|
||||
className="mr-xs"
|
||||
onClick={() => openUpdateProjectRegionPage(rowId)}
|
||||
icon={<FaExternalLinkAlt />}
|
||||
>
|
||||
Update Project Region
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</IndicatorCard>
|
||||
</div>
|
||||
<AccelerateProject
|
||||
isLoading={databaseCheckLoading}
|
||||
onReCheckClick={() => {
|
||||
refetch();
|
||||
}}
|
||||
onUpdateRegionClick={() => {
|
||||
openUpdateProjectRegionPage(rowId);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
isCloudConsole(globals) && (
|
||||
<Button
|
||||
|
@ -0,0 +1,57 @@
|
||||
import { Button } from '../../../../../new-components/Button';
|
||||
import { FaRedoAlt, FaExternalLinkAlt } from 'react-icons/fa';
|
||||
import { IndicatorCard } from '../../../../../new-components/IndicatorCard';
|
||||
import { LearnMoreLink } from '../../../../../new-components/LearnMoreLink';
|
||||
import React from 'react';
|
||||
|
||||
export function AccelerateProject({
|
||||
isLoading,
|
||||
onReCheckClick,
|
||||
onUpdateRegionClick,
|
||||
}: {
|
||||
isLoading: boolean;
|
||||
onReCheckClick: () => void;
|
||||
onUpdateRegionClick: () => void;
|
||||
}) {
|
||||
return (
|
||||
<div className="mt-xs">
|
||||
<IndicatorCard
|
||||
status="negative"
|
||||
headline="Accelerate your Hasura Project"
|
||||
>
|
||||
<div className="flex items-center flex-row">
|
||||
<span>
|
||||
Databases marked with “Elevated Latency” indicate that it took us
|
||||
over 200 ms for this Hasura project to communicate with your
|
||||
database. These conditions generally happen when databases and
|
||||
projects are in geographically distant regions. This can cause API
|
||||
and subsequently application performance issues. We want your
|
||||
GraphQL APIs to be <b>lightning fast</b>, therefore we recommend
|
||||
that you either deploy your Hasura project in the same region as
|
||||
your database or select a database instance that's closer to
|
||||
where you've deployed Hasura.
|
||||
<LearnMoreLink href="https://hasura.io/docs/latest/projects/regions/#changing-region-of-an-existing-project" />
|
||||
</span>
|
||||
<div className="flex items-center flex-row ml-xs">
|
||||
<Button
|
||||
className="mr-xs"
|
||||
onClick={onReCheckClick}
|
||||
isLoading={isLoading}
|
||||
loadingText="Measuring Latencies..."
|
||||
icon={<FaRedoAlt />}
|
||||
>
|
||||
Re-check Database Latency
|
||||
</Button>
|
||||
<Button
|
||||
className="mr-xs"
|
||||
onClick={onUpdateRegionClick}
|
||||
icon={<FaExternalLinkAlt />}
|
||||
>
|
||||
Update Project Region
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</IndicatorCard>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -23,9 +23,9 @@ export const DisplayDetails = ({
|
||||
<div className="flex justify-start">
|
||||
<div
|
||||
className={clsx(
|
||||
'max-w-2xl',
|
||||
'max-w-md',
|
||||
isExpanded
|
||||
? 'whitespace-pre-line'
|
||||
? 'whitespace-pre-line max-w-xl'
|
||||
: 'overflow-hidden text-ellipsis whitespace-nowrap'
|
||||
)}
|
||||
title={version}
|
||||
|
@ -0,0 +1,38 @@
|
||||
import { FaCheck, FaExclamationTriangle, FaMinusCircle } from 'react-icons/fa';
|
||||
import { Latency } from '../../../types';
|
||||
import { Badge } from '../../../../../new-components/Badge';
|
||||
import React from 'react';
|
||||
|
||||
export const LatencyBadge = ({
|
||||
latencies,
|
||||
dataSourceName,
|
||||
}: {
|
||||
latencies: Latency[];
|
||||
dataSourceName: string;
|
||||
}) => {
|
||||
const currentDataSourceLatencyInfo = latencies.find(
|
||||
latencyInfo => latencyInfo.dataSourceName === dataSourceName
|
||||
);
|
||||
|
||||
if (!currentDataSourceLatencyInfo) return null;
|
||||
|
||||
if (currentDataSourceLatencyInfo.avgLatency < 100)
|
||||
return (
|
||||
<Badge color="green">
|
||||
<FaCheck className="mr-xs" /> Connection
|
||||
</Badge>
|
||||
);
|
||||
|
||||
if (currentDataSourceLatencyInfo.avgLatency < 200)
|
||||
return (
|
||||
<Badge color="yellow">
|
||||
<FaMinusCircle className="mr-xs" /> Acceptable
|
||||
</Badge>
|
||||
);
|
||||
|
||||
return (
|
||||
<Badge color="red">
|
||||
<FaExclamationTriangle className="mr-xs" /> Elevated Latency
|
||||
</Badge>
|
||||
);
|
||||
};
|
@ -0,0 +1,4 @@
|
||||
export { AccelerateProject } from './AccelerateProject';
|
||||
export { DisplayDetails, Details } from './Details';
|
||||
export { InconsistentSourceDetails } from './InconsistentSourceDetails';
|
||||
export { LatencyBadge } from './LatencyBadge';
|
Loading…
Reference in New Issue
Block a user