Skip to content

Commit 5763c13

Browse files
committed
fix: inconsistient behavior with pull and redeploy button via project table
1 parent 4acc604 commit 5763c13

4 files changed

Lines changed: 306 additions & 235 deletions

File tree

cli/go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ require (
1010
github.com/fatih/color v1.18.0
1111
github.com/getarcaneapp/arcane/types v1.15.3
1212
github.com/go-viper/mapstructure/v2 v2.5.0
13+
github.com/mattn/go-runewidth v0.0.20
1314
github.com/sirupsen/logrus v1.9.4
1415
github.com/spf13/cobra v1.10.2
1516
github.com/spf13/pflag v1.0.10
@@ -37,7 +38,6 @@ require (
3738
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
3839
github.com/mattn/go-colorable v0.1.14 // indirect
3940
github.com/mattn/go-isatty v0.0.20 // indirect
40-
github.com/mattn/go-runewidth v0.0.20 // indirect
4141
github.com/mattn/go-shellwords v1.0.12 // indirect
4242
github.com/moby/docker-image-spec v1.3.1 // indirect
4343
github.com/muesli/cancelreader v0.2.2 // indirect
Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
import { openConfirmDialog } from '$lib/components/confirm-dialog';
2+
import { m } from '$lib/paraglide/messages';
3+
import { deployOptionsStore } from '$lib/stores/deploy-options.store.svelte';
4+
import { gitOpsSyncService } from '$lib/services/gitops-sync-service';
5+
import { projectService } from '$lib/services/project-service';
6+
import type { SearchPaginationSortRequest } from '$lib/types/pagination.type';
7+
import { handleApiResultWithCallbacks } from '$lib/utils/api.util';
8+
import { tryCatch } from '$lib/utils/try-catch';
9+
import { toast } from 'svelte-sonner';
10+
import type { ActionStatus } from './projects-table.helpers';
11+
12+
type BulkLoadingState = {
13+
up: boolean;
14+
down: boolean;
15+
redeploy: boolean;
16+
};
17+
18+
type ActionDeps = {
19+
getRequestOptions: () => SearchPaginationSortRequest;
20+
refreshProjects: (options?: SearchPaginationSortRequest) => Promise<void>;
21+
setSelectedIds: (next: string[]) => void;
22+
actionStatus: Record<string, ActionStatus>;
23+
isBulkLoading: BulkLoadingState;
24+
getEnvId: () => string | undefined;
25+
};
26+
27+
type ProjectActionKind = 'start' | 'stop' | 'restart' | 'redeploy';
28+
29+
type ProjectActionConfig = {
30+
status: ActionStatus;
31+
run: (id: string) => Promise<unknown>;
32+
success: () => string;
33+
failure: () => string;
34+
};
35+
36+
type BulkActionConfig = {
37+
title: (count: number) => string;
38+
message: (count: number) => string;
39+
label: string;
40+
loadingKey: keyof BulkLoadingState;
41+
run: (id: string) => Promise<unknown>;
42+
success: (count: number) => string;
43+
partial: (success: number, total: number, failed: number) => string;
44+
failure: () => string;
45+
destructive?: boolean;
46+
};
47+
48+
type DestroyConfirmResult = {
49+
checkboxes?: {
50+
volumes?: boolean;
51+
files?: boolean;
52+
};
53+
volumes?: boolean;
54+
files?: boolean;
55+
};
56+
57+
type ProjectActions = {
58+
performProjectAction: (action: ProjectActionKind, id: string) => Promise<void>;
59+
handleDestroyProject: (id: string) => Promise<void>;
60+
handleSyncFromGit: (projectId: string, gitOpsSyncId: string) => Promise<void>;
61+
handleBulkUp: (ids: string[]) => Promise<void>;
62+
handleBulkDown: (ids: string[]) => Promise<void>;
63+
handleBulkRedeploy: (ids: string[]) => Promise<void>;
64+
};
65+
66+
const projectActionConfigs: Record<ProjectActionKind, ProjectActionConfig> = {
67+
start: {
68+
status: 'starting',
69+
run: (id) => projectService.deployProject(id, deployOptionsStore.getRequestOptions()),
70+
success: () => m.compose_start_success(),
71+
failure: () => m.compose_start_failed()
72+
},
73+
stop: {
74+
status: 'stopping',
75+
run: (id) => projectService.downProject(id),
76+
success: () => m.compose_stop_success(),
77+
failure: () => m.compose_stop_failed()
78+
},
79+
restart: {
80+
status: 'restarting',
81+
run: (id) => projectService.restartProject(id),
82+
success: () => m.compose_restart_success(),
83+
failure: () => m.compose_restart_failed()
84+
},
85+
redeploy: {
86+
status: 'redeploying',
87+
run: (id) => projectService.redeployProject(id),
88+
success: () => m.compose_pull_success(),
89+
failure: () => m.compose_pull_failed()
90+
}
91+
};
92+
93+
export function createProjectActions({
94+
getRequestOptions,
95+
refreshProjects,
96+
setSelectedIds,
97+
actionStatus,
98+
isBulkLoading,
99+
getEnvId
100+
}: ActionDeps): ProjectActions {
101+
async function performProjectAction(action: ProjectActionKind, id: string): Promise<void> {
102+
const config = projectActionConfigs[action];
103+
actionStatus[id] = config.status;
104+
105+
try {
106+
handleApiResultWithCallbacks({
107+
result: await tryCatch(config.run(id)),
108+
message: config.failure(),
109+
setLoadingState: (value) => {
110+
actionStatus[id] = value ? config.status : '';
111+
},
112+
onSuccess: async () => {
113+
toast.success(config.success());
114+
await refreshProjects();
115+
}
116+
});
117+
} catch (error) {
118+
toast.error(m.common_action_failed());
119+
actionStatus[id] = '';
120+
}
121+
}
122+
123+
async function handleDestroyProject(id: string): Promise<void> {
124+
openConfirmDialog({
125+
title: m.common_confirm_removal_title(),
126+
message: m.compose_confirm_removal_message(),
127+
checkboxes: [
128+
{
129+
id: 'volumes',
130+
label: m.confirm_remove_volumes_warning(),
131+
initialState: false
132+
},
133+
{
134+
id: 'files',
135+
label: m.confirm_remove_project_files(),
136+
initialState: false
137+
}
138+
],
139+
confirm: {
140+
label: m.compose_destroy(),
141+
destructive: true,
142+
action: async (result: DestroyConfirmResult) => {
143+
const removeVolumes = !!(result?.checkboxes?.volumes ?? result?.volumes);
144+
const removeFiles = !!(result?.checkboxes?.files ?? result?.files);
145+
actionStatus[id] = 'destroying';
146+
147+
handleApiResultWithCallbacks({
148+
result: await tryCatch(projectService.destroyProject(id, removeVolumes, removeFiles)),
149+
message: m.compose_destroy_failed(),
150+
setLoadingState: (value) => {
151+
actionStatus[id] = value ? 'destroying' : '';
152+
},
153+
onSuccess: async () => {
154+
toast.success(m.compose_destroy_success());
155+
await refreshProjects();
156+
}
157+
});
158+
}
159+
}
160+
});
161+
}
162+
163+
async function handleSyncFromGit(projectId: string, gitOpsSyncId: string): Promise<void> {
164+
const envId = getEnvId();
165+
if (!envId) return;
166+
167+
actionStatus[projectId] = 'syncing';
168+
const result = await tryCatch(gitOpsSyncService.performSync(envId, gitOpsSyncId));
169+
170+
handleApiResultWithCallbacks({
171+
result,
172+
message: m.git_sync_failed(),
173+
setLoadingState: (value) => {
174+
actionStatus[projectId] = value ? 'syncing' : '';
175+
},
176+
onSuccess: async () => {
177+
toast.success(m.git_sync_success());
178+
await refreshProjects();
179+
}
180+
});
181+
}
182+
183+
async function runBulkAction(ids: string[], config: BulkActionConfig): Promise<void> {
184+
if (!ids || ids.length === 0) return;
185+
186+
openConfirmDialog({
187+
title: config.title(ids.length),
188+
message: config.message(ids.length),
189+
confirm: {
190+
label: config.label,
191+
destructive: config.destructive ?? false,
192+
action: async () => {
193+
isBulkLoading[config.loadingKey] = true;
194+
195+
const results = await Promise.allSettled(ids.map((id) => config.run(id)));
196+
197+
const successCount = results.filter((result) => result.status === 'fulfilled').length;
198+
const failureCount = results.length - successCount;
199+
200+
isBulkLoading[config.loadingKey] = false;
201+
202+
if (successCount === ids.length) {
203+
toast.success(config.success(successCount));
204+
} else if (successCount > 0) {
205+
toast.warning(config.partial(successCount, ids.length, failureCount));
206+
} else {
207+
toast.error(config.failure());
208+
}
209+
210+
await refreshProjects(getRequestOptions());
211+
setSelectedIds([]);
212+
}
213+
}
214+
});
215+
}
216+
217+
async function handleBulkUp(ids: string[]): Promise<void> {
218+
await runBulkAction(ids, {
219+
title: (count) => m.projects_bulk_up_confirm_title({ count }),
220+
message: (count) => m.projects_bulk_up_confirm_message({ count }),
221+
label: m.common_up(),
222+
loadingKey: 'up',
223+
run: (id) => projectService.deployProject(id, deployOptionsStore.getRequestOptions()),
224+
success: (count) => m.projects_bulk_up_success({ count }),
225+
partial: (success, total, failed) => m.projects_bulk_up_partial({ success, total, failed }),
226+
failure: () => m.compose_start_failed()
227+
});
228+
}
229+
230+
async function handleBulkDown(ids: string[]): Promise<void> {
231+
await runBulkAction(ids, {
232+
title: (count) => m.projects_bulk_down_confirm_title({ count }),
233+
message: (count) => m.projects_bulk_down_confirm_message({ count }),
234+
label: m.common_down(),
235+
loadingKey: 'down',
236+
run: (id) => projectService.downProject(id),
237+
success: (count) => m.projects_bulk_down_success({ count }),
238+
partial: (success, total, failed) => m.projects_bulk_down_partial({ success, total, failed }),
239+
failure: () => m.compose_stop_failed()
240+
});
241+
}
242+
243+
async function handleBulkRedeploy(ids: string[]): Promise<void> {
244+
await runBulkAction(ids, {
245+
title: (count) => m.projects_bulk_redeploy_confirm_title({ count }),
246+
message: (count) => m.projects_bulk_redeploy_confirm_message({ count }),
247+
label: m.compose_pull_redeploy(),
248+
loadingKey: 'redeploy',
249+
run: (id) => projectService.redeployProject(id),
250+
success: (count) => m.projects_bulk_redeploy_success({ count }),
251+
partial: (success, total, failed) => m.projects_bulk_redeploy_partial({ success, total, failed }),
252+
failure: () => m.compose_pull_failed()
253+
});
254+
}
255+
256+
return {
257+
performProjectAction,
258+
handleDestroyProject,
259+
handleSyncFromGit,
260+
handleBulkUp,
261+
handleBulkDown,
262+
handleBulkRedeploy
263+
};
264+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export type ActionStatus = 'starting' | 'stopping' | 'restarting' | 'redeploying' | 'destroying' | 'syncing' | '';

0 commit comments

Comments
 (0)