Skip to content

Commit c077a46

Browse files
committed
fix: Use the word Remove with Self-Managed
https://harperdb.atlassian.net/browse/STUDIO-450
1 parent c90c1b1 commit c077a46

File tree

3 files changed

+93
-94
lines changed

3 files changed

+93
-94
lines changed

src/components/ConfirmDeletionModal.tsx

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export function ConfirmDeletionModal({
1515
typeOfThingBeingDeleted,
1616
nameOfThingBeingDeleted,
1717
isModalOpen,
18+
hideDataLossWarning,
1819
setIsModalOpen,
1920
deletionConfirmed,
2021
deletionPending,
@@ -25,6 +26,7 @@ export function ConfirmDeletionModal({
2526
typeOfThingBeingDeleted: string;
2627
nameOfThingBeingDeleted?: string;
2728
isModalOpen: boolean;
29+
hideDataLossWarning?: boolean;
2830
setIsModalOpen: (isOpen: boolean) => void;
2931
deletionConfirmed: () => void;
3032
deletionPending: boolean;
@@ -41,18 +43,22 @@ export function ConfirmDeletionModal({
4143
)}
4244
<DialogContent className="sm:max-w-[750px]">
4345
<DialogHeader>
44-
<DialogTitle>Are you sure you want to {transitiveVerb.toLowerCase()} this {typeOfThingBeingDeleted}?</DialogTitle>
46+
<DialogTitle>
47+
Are you sure you want
48+
to {transitiveVerb.toLowerCase()} this {typeOfThingBeingDeleted}?
49+
</DialogTitle>
4550
<DialogDescription>This action cannot be undone.</DialogDescription>
4651
</DialogHeader>
47-
<div className="p-3 my-5 text-white rounded-md bg-amber-600">
52+
{!hideDataLossWarning && (<div className="p-3 my-5 text-white rounded-md bg-amber-600">
4853
<p className="flex space-x-1 font-semibold align-baseline">
4954
<TriangleAlert className="inline-block size-5" /> <span>Warning</span>
5055
</p>
5156
<p className="pt-2 text-base">
52-
By {presentParticiple.toLowerCase()} {typeOfThingBeingDeleted} <span className="font-semibold">{nameOfThingBeingDeleted} </span>
57+
By {presentParticiple.toLowerCase()} {typeOfThingBeingDeleted}
58+
<span className="font-semibold">{nameOfThingBeingDeleted} </span>
5359
you will lose the data stored in it permanently.
5460
</p>
55-
</div>
61+
</div>)}
5662
<DialogFooter>
5763
<div className="flex justify-center space-x-5">
5864
<Button className="rounded-full" onClick={() => setIsModalOpen(false)}>

src/features/clusters/ClustersList.tsx

Lines changed: 2 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,26 @@
1-
import { ConfirmDeletionModal } from '@/components/ConfirmDeletionModal';
21
import { SubNavMenu } from '@/components/SubNavMenu';
32
import { Badge } from '@/components/ui/badge';
43
import { Button } from '@/components/ui/button';
54
import { Input } from '@/components/ui/input';
65
import { renderBadgeStatusVariant } from '@/components/ui/utils/badgeStatus';
76
import { ClusterCard } from '@/features/clusters/components/ClusterCard';
8-
import { useTerminateClusterMutation } from '@/features/clusters/mutations/terminateCluster';
97
import { getOrganizationQueryOptions } from '@/features/organization/queries/getOrganizationQuery';
108
import { useOrganizationClusterPermissions } from '@/hooks/usePermissions';
119
import { Cluster } from '@/lib/api.patch';
1210
import { byClusterStatusThenName } from '@/lib/arrays/sort/byClusterStatusThenName';
1311
import { groupBy } from '@/lib/groupBy';
1412
import { capitalizeWords } from '@/lib/string/capitalizeWords';
1513
import { curryFilterByFuzzySearch } from '@/lib/string/filterByFuzzySearch';
16-
import { queryKeys } from '@/react-query/constants';
17-
import { useQueryClient, useSuspenseQuery } from '@tanstack/react-query';
14+
import { useSuspenseQuery } from '@tanstack/react-query';
1815
import { Link, useParams } from '@tanstack/react-router';
1916
import { Plus } from 'lucide-react';
2017
import { FormEvent, useCallback, useMemo, useState } from 'react';
21-
import { toast } from 'sonner';
2218

2319
export function ClustersList() {
24-
const queryClient = useQueryClient();
2520
const { organizationId }: { organizationId: string } = useParams({ strict: false });
2621
const { create } = useOrganizationClusterPermissions(organizationId);
2722
const { data: orgInfo, isSuccess } = useSuspenseQuery(getOrganizationQueryOptions(organizationId));
28-
const { mutate: terminateCluster, isPending: isTerminateClusterPending } = useTerminateClusterMutation();
2923

30-
const [isTerminateClusterModalOpen, setIsTerminateClusterModalOpen] = useState(false);
31-
const [terminateClusterInfo, setTerminateClusterInfo] = useState({
32-
id: '',
33-
name: '',
34-
});
3524
const [filterByNameValue, setFilterByNameValue] = useState('');
3625

3726
const onFilterByNameChanged = useCallback((e: FormEvent<HTMLInputElement>) => {
@@ -53,47 +42,6 @@ export function ClustersList() {
5342
};
5443
}, [filterByNameValue, orgInfo?.clusters]);
5544

56-
const handleTerminatedCluster = useCallback(
57-
(clusterInfo: { id: string; name: string }) => {
58-
if (clusterInfo) {
59-
terminateCluster(clusterInfo.id, {
60-
onSuccess: () => {
61-
toast.success('Success', {
62-
description: `Cluster successfully terminated.`,
63-
duration: 5000,
64-
action: {
65-
label: 'Dismiss',
66-
onClick: () => toast.dismiss(),
67-
},
68-
});
69-
void queryClient.invalidateQueries({ queryKey: [queryKeys.organization], refetchType: 'active' });
70-
setIsTerminateClusterModalOpen(false);
71-
},
72-
onError: () => {
73-
toast.error('Error', {
74-
description: `Failed to terminate cluster: ${clusterInfo.name}.`,
75-
duration: 5000,
76-
action: {
77-
label: 'Dismiss',
78-
onClick: () => toast.dismiss(),
79-
},
80-
});
81-
setIsTerminateClusterModalOpen(false);
82-
},
83-
});
84-
}
85-
},
86-
[terminateCluster, queryClient, setIsTerminateClusterModalOpen]
87-
);
88-
89-
const onTerminateClusterModal = useCallback((cluster: Cluster) => {
90-
setTerminateClusterInfo({
91-
id: cluster.id,
92-
name: cluster.name,
93-
});
94-
setIsTerminateClusterModalOpen(true);
95-
}, []);
96-
9745
return (
9846
<>
9947
<SubNavMenu>
@@ -132,7 +80,7 @@ export function ClustersList() {
13280
<div className="grid grid-cols-1 gap-4 md:grid-cols-12 mb-4">
13381
{clustersData.groups[clusterStatus].map((cluster) => (
13482
<div key={cluster.id} className="cols-span-1 md:col-span-4 lg:col-span-3 2xl:col-span-2">
135-
<ClusterCard cluster={cluster} onTerminateClusterModal={onTerminateClusterModal} />
83+
<ClusterCard cluster={cluster} />
13684
</div>
13785
))}
13886
</div>
@@ -162,16 +110,6 @@ export function ClustersList() {
162110
</div>
163111
)}
164112
</section>
165-
<ConfirmDeletionModal
166-
typeOfThingBeingDeleted="cluster"
167-
transitiveVerb="Terminate"
168-
presentParticiple="Terminating"
169-
nameOfThingBeingDeleted={terminateClusterInfo.name}
170-
isModalOpen={isTerminateClusterModalOpen}
171-
setIsModalOpen={(isOpen: boolean) => setIsTerminateClusterModalOpen(isOpen)}
172-
deletionConfirmed={() => handleTerminatedCluster(terminateClusterInfo)}
173-
deletionPending={isTerminateClusterPending}
174-
/>
175113
</>
176114
);
177115
}

src/features/clusters/components/ClusterCard.tsx

Lines changed: 81 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { ConfirmDeletionModal } from '@/components/ConfirmDeletionModal';
12
import { ProgressBar } from '@/components/ProgressBar';
23
import { Badge } from '@/components/ui/badge';
34
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
@@ -13,14 +14,17 @@ import { isBeingUpdated, isRunning } from '@/components/ui/utils/badgeStatus';
1314
import { useInstanceClient } from '@/config/useInstanceClient';
1415
import { getClusterInfo, getClusterInfoQueryOptions } from '@/features/cluster/queries/getClusterInfoQuery';
1516
import { ClusterCardAction } from '@/features/clusters/components/ClusterCardAction';
17+
import { useTerminateClusterMutation } from '@/features/clusters/mutations/terminateCluster';
1618
import { onInstanceLogoutSubmit } from '@/features/instance/operations/mutations/onInstanceLogoutSubmit';
1719
import { useInstanceAuth } from '@/hooks/useAuth';
1820
import { useOrganizationClusterPermissions } from '@/hooks/usePermissions';
1921
import { Cluster } from '@/lib/api.patch';
2022
import { excludeFalsy } from '@/lib/arrays/excludeFalsy';
2123
import { authStore } from '@/lib/authStore';
24+
import { capitalizeWords } from '@/lib/string/capitalizeWords';
2225
import { getOperationsUrlForCluster } from '@/lib/urls/getOperationsUrlForCluster';
23-
import { useQuery } from '@tanstack/react-query';
26+
import { queryKeys } from '@/react-query/constants';
27+
import { useQuery, useQueryClient } from '@tanstack/react-query';
2428
import { Link } from '@tanstack/react-router';
2529
import { CopyIcon, Ellipsis } from 'lucide-react';
2630
import { useCallback, useMemo, useState } from 'react';
@@ -29,40 +33,45 @@ import { toast } from 'sonner';
2933
const activeClusterStatuses = ['RUNNING'];
3034
const deletedClusterStatuses = ['TERMINATING', 'TERMINATED', 'REMOVED'];
3135

32-
export function ClusterCard({
33-
cluster,
34-
onTerminateClusterModal,
35-
}: {
36-
cluster: Cluster;
37-
onTerminateClusterModal: (cluster: Cluster) => void;
38-
}) {
39-
const { view, update, remove } = useOrganizationClusterPermissions(cluster.organizationId, cluster.id);
36+
export function ClusterCard({ cluster }: { cluster: Cluster; }) {
37+
const queryClient = useQueryClient();
38+
const operationsUrl = useMemo(() => getOperationsUrlForCluster(cluster), [cluster]);
39+
const instanceClient = useInstanceClient(operationsUrl);
4040
const auth = useInstanceAuth(cluster.id);
41-
const isUpdating = isBeingUpdated(cluster.status);
42-
const { data: clusterById } = useQuery(
43-
getClusterInfoQueryOptions(isUpdating && cluster.id, 2000),
44-
);
41+
42+
const { view, update, remove } = useOrganizationClusterPermissions(cluster.organizationId, cluster.id);
43+
const { mutate: terminateCluster, isPending: isTerminateClusterPending } = useTerminateClusterMutation();
44+
45+
const [signingOut, setSigningOut] = useState(false);
46+
const [isTerminateClusterModalOpen, setIsTerminateClusterModalOpen] = useState(false);
4547

4648
const isActive = useMemo(() => cluster.status && activeClusterStatuses.includes(cluster.status), [cluster.status]);
49+
const isSelfManaged = useMemo(() => !!cluster?.plans?.[0]?.planId?.startsWith('self-hosted'), [cluster]);
4750
const isTerminated = useMemo(
4851
() => cluster.status && deletedClusterStatuses.includes(cluster.status),
4952
[cluster.status],
5053
);
54+
55+
const isUpdating = isBeingUpdated(cluster.status);
56+
const { data: clusterById } = useQuery(
57+
getClusterInfoQueryOptions(isUpdating && cluster.id, 2000),
58+
);
5159
const updating = useMemo(() => {
5260
if (isUpdating && clusterById?.instances) {
5361
const updating = clusterById.instances.reduce((total, instance) =>
5462
total + (isBeingUpdated(instance.status) ? 1 : 0), 0);
5563
const running = clusterById.instances.reduce((total, instance) =>
5664
total + (isRunning(instance.status) ? 1 : 0), 0);
65+
const current = running;
66+
const total = updating + running;
5767
return {
58-
current: running,
59-
total: updating + running,
68+
width: `${current === 0 ? 0 : (current / total * 100)}%`,
69+
text: total > 0
70+
? `${current} / ${total}`
71+
: `${capitalizeWords(cluster.status ?? 'Updating')}...`,
6072
};
6173
}
62-
}, [clusterById, isUpdating]);
63-
const operationsUrl = useMemo(() => getOperationsUrlForCluster(cluster), [cluster]);
64-
const instanceClient = useInstanceClient(operationsUrl);
65-
const [signingOut, setSigningOut] = useState(false);
74+
}, [cluster.status, clusterById?.instances, isUpdating]);
6675

6776
const onSignOutClick = useCallback(async () => {
6877
setSigningOut(true);
@@ -79,9 +88,43 @@ export function ClusterCard({
7988
}
8089
authStore.setUserForEntity(cluster, null);
8190
}, [cluster, instanceClient]);
82-
const onTerminateClick = useCallback(() => {
83-
onTerminateClusterModal(cluster);
84-
}, [cluster, onTerminateClusterModal]);
91+
92+
const onTerminateClick = useCallback(() => setIsTerminateClusterModalOpen(true), []);
93+
94+
const handleTerminatedCluster = useCallback(() => {
95+
terminateCluster(cluster.id, {
96+
onSuccess: () => {
97+
toast.success('Success', {
98+
description: isSelfManaged
99+
? `Cluster successfully removed.`
100+
: `Cluster successfully terminated.`,
101+
duration: 5000,
102+
action: {
103+
label: 'Dismiss',
104+
onClick: () => toast.dismiss(),
105+
},
106+
});
107+
void queryClient.invalidateQueries({
108+
queryKey: [queryKeys.organization],
109+
refetchType: 'active',
110+
});
111+
setIsTerminateClusterModalOpen(false);
112+
},
113+
onError: () => {
114+
toast.error('Error', {
115+
description: isSelfManaged
116+
? `Failed to remove cluster: ${cluster.name}`
117+
: `Failed to terminate cluster: ${cluster.name}.`,
118+
duration: 5000,
119+
action: {
120+
label: 'Dismiss',
121+
onClick: () => toast.dismiss(),
122+
},
123+
});
124+
setIsTerminateClusterModalOpen(false);
125+
},
126+
});
127+
}, [terminateCluster, cluster.id, cluster.name, isSelfManaged, queryClient]);
85128

86129
const onCopyFQDNClick = useCallback(() => {
87130
navigator.clipboard.writeText(cluster.fqdn || '');
@@ -121,7 +164,7 @@ export function ClusterCard({
121164
),
122165
!isTerminated && remove && (
123166
<DropdownMenuItem className="focus:bg-red/70 focus:text-white" onClick={onTerminateClick}>
124-
Terminate
167+
{isSelfManaged ? 'Remove' : 'Terminate'}
125168
</DropdownMenuItem>
126169
),
127170
].filter(excludeFalsy);
@@ -168,16 +211,28 @@ export function ClusterCard({
168211
</CardTitle>
169212
</CardHeader>
170213
<CardContent className="flex justify-between">
171-
{updating && (
214+
{updating && cluster.status && (
172215
<ProgressBar
173216
animated={true}
174217
className="bg-yellow-800/60"
175-
width={(updating.current === 0 ? 0 : (updating.current / updating.total * 100)) + '%'}
176-
placeholder="updating..."
218+
width={updating.width}
219+
placeholder={updating.text}
177220
/>
178221
)}
179222
{isActive && view && <ClusterCardAction cluster={cluster} />}
180223
</CardContent>
224+
225+
<ConfirmDeletionModal
226+
typeOfThingBeingDeleted="cluster"
227+
transitiveVerb={isSelfManaged ? 'Remove' : 'Terminate'}
228+
presentParticiple={isSelfManaged ? 'Removing' : 'Terminating'}
229+
nameOfThingBeingDeleted={cluster.name}
230+
isModalOpen={isTerminateClusterModalOpen}
231+
hideDataLossWarning={isSelfManaged}
232+
setIsModalOpen={(isOpen: boolean) => setIsTerminateClusterModalOpen(isOpen)}
233+
deletionConfirmed={handleTerminatedCluster}
234+
deletionPending={isTerminateClusterPending}
235+
/>
181236
</Card>
182237
);
183238
}

0 commit comments

Comments
 (0)