Skip to content

Commit d9a5ea0

Browse files
sergeyteleshevmr-anton-tdariamarutkina
authored
CB-6115 Add dispose to FormState instances (#3317)
* CB-6115 adds dispose API for forms * CB-6115 adds git workspace helper extension for repo recommendations * CB-6115 pr fixes * CB-6115 adds dispose methods to tables * CB-6115 eslint fix * CB-6115 fixes dispose leaks for freshly created states * CB-6115 fixes infinite loading of state --------- Co-authored-by: mr-anton-t <[email protected]> Co-authored-by: Daria Marutkina <[email protected]>
1 parent 06e2aa9 commit d9a5ea0

File tree

11 files changed

+78
-24
lines changed

11 files changed

+78
-24
lines changed

webapp/packages/core-ui/src/Form/FormPart.ts

+1
Original file line numberDiff line numberDiff line change
@@ -174,4 +174,5 @@ export abstract class FormPart<TPartState, TFormState = any> implements IFormPar
174174

175175
protected abstract loader(): Promise<void>;
176176
protected abstract saveChanges(data: IFormState<TFormState>, contexts: IExecutionContextProvider<IFormState<TFormState>>): Promise<void>;
177+
dispose(): void | Promise<void> {}
177178
}

webapp/packages/core-ui/src/Form/FormState.ts

+24-5
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export class FormState<TState> implements IFormState<TState> {
2929
statusMessage: string | string[] | null;
3030
statusType: ENotificationType | null;
3131

32-
promise: Promise<any> | null;
32+
savingPromise: Promise<any> | null;
3333

3434
get isDisabled(): boolean {
3535
return this.partsValues.some(part => part.isSaving || part?.isLoading?.());
@@ -48,6 +48,7 @@ export class FormState<TState> implements IFormState<TState> {
4848
readonly submitTask: IExecutor<IFormState<TState>>;
4949
readonly formatTask: IExecutor<IFormState<TState>>;
5050
readonly validationTask: IExecutor<IFormState<TState>>;
51+
readonly disposeTask: IExecutor<IFormState<TState>>;
5152

5253
constructor(serviceProvider: IServiceProvider, service: FormBaseService<TState, any>, state: TState) {
5354
this.id = uuid();
@@ -61,7 +62,7 @@ export class FormState<TState> implements IFormState<TState> {
6162
this.statusMessage = null;
6263
this.statusType = null;
6364

64-
this.promise = null;
65+
this.savingPromise = null;
6566

6667
this.formStateTask = new Executor<TState>(state, () => true);
6768
this.formStateTask.addCollection(service.onState).addPostHandler(this.updateFormState.bind(this));
@@ -78,14 +79,16 @@ export class FormState<TState> implements IFormState<TState> {
7879
this.submitTask = new Executor(this as IFormState<TState>, () => true);
7980
this.submitTask.addCollection(service.onSubmit).before(this.validationTask);
8081

82+
this.disposeTask = new Executor(this as IFormState<TState>, () => true);
83+
8184
this.dataContext.set(DATA_CONTEXT_LOADABLE_STATE, loadableStateContext(), this.id);
8285
this.dataContext.set(DATA_CONTEXT_FORM_STATE, this, this.id);
8386
dataContextAddDIProvider(this.dataContext, serviceProvider, this.id);
8487

8588
makeObservable<this, 'updateFormState'>(this, {
8689
mode: observable,
8790
parts: observable.ref,
88-
promise: observable.ref,
91+
savingPromise: observable.ref,
8992
state: observable,
9093
isSaving: computed,
9194
exception: computed,
@@ -171,14 +174,18 @@ export class FormState<TState> implements IFormState<TState> {
171174

172175
async save(): Promise<boolean> {
173176
try {
174-
const context = await this.submitTask.execute(this);
177+
this.savingPromise = this.submitTask.execute(this);
178+
const context = await this.savingPromise;
175179

176180
if (ExecutorInterrupter.isInterrupted(context)) {
177181
return false;
178182
}
179183

180184
return true;
181-
} catch (exception: any) {}
185+
} catch (exception: any) {
186+
} finally {
187+
this.savingPromise = null;
188+
}
182189

183190
return false;
184191
}
@@ -202,4 +209,16 @@ export class FormState<TState> implements IFormState<TState> {
202209
this.statusMessage = context.statusMessage;
203210
this.statusType = context.statusType;
204211
}
212+
213+
async dispose(): Promise<void> {
214+
if (this.savingPromise) {
215+
await this.savingPromise;
216+
}
217+
218+
for (const part of this.parts.values()) {
219+
await part.dispose();
220+
}
221+
222+
await this.disposeTask.execute(this);
223+
}
205224
}

webapp/packages/core-ui/src/Form/IFormPart.ts

+1
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,5 @@ export interface IFormPart<TState> extends ILoadableState {
1717

1818
load(): Promise<void>;
1919
reset(): void;
20+
dispose(): void | Promise<void>;
2021
}

webapp/packages/core-ui/src/Form/IFormState.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export interface IFormState<TState> {
2525
readonly isDisabled: boolean;
2626
readonly exception: Error | (Error | null)[] | null;
2727

28-
readonly promise: Promise<any> | null;
28+
readonly savingPromise: Promise<any> | null;
2929

3030
readonly statusMessage: string | string[] | null;
3131
readonly statusType: ENotificationType | null;
@@ -49,4 +49,5 @@ export interface IFormState<TState> {
4949
save(): Promise<boolean>;
5050
reset(): void;
5151
cancel(): void;
52+
dispose(): void | Promise<void>;
5253
}

webapp/packages/plugin-administration/src/ConfigurationWizard/ConfigurationWizardPagesBootstrapService.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*
22
* CloudBeaver - Cloud Database Manager
3-
* Copyright (C) 2020-2024 DBeaver Corp and others
3+
* Copyright (C) 2020-2025 DBeaver Corp and others
44
*
55
* Licensed under the Apache License, Version 2.0.
66
* you may not use this file except in compliance with the License.
@@ -95,6 +95,7 @@ export class ConfigurationWizardPagesBootstrapService extends Bootstrap {
9595
onLoad: () => {
9696
this.serverConfigurationFormStateManager.create();
9797
},
98+
onDeActivate: this.serverConfigurationFormStateManager.destroy.bind(this.serverConfigurationFormStateManager),
9899
canDeActivate: async configurationWizard => {
99100
const state = this.serverConfigurationFormStateManager.formState;
100101

webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationFormStateManager.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*
22
* CloudBeaver - Cloud Database Manager
3-
* Copyright (C) 2020-2024 DBeaver Corp and others
3+
* Copyright (C) 2020-2025 DBeaver Corp and others
44
*
55
* Licensed under the Apache License, Version 2.0.
66
* you may not use this file except in compliance with the License.
@@ -38,6 +38,7 @@ export class ServerConfigurationFormStateManager {
3838

3939
destroy() {
4040
if (this.formState) {
41+
this.formState?.dispose();
4142
this.formState = null;
4243
}
4344
}

webapp/packages/plugin-authentication-administration/src/Administration/Users/Teams/TeamsForm/useTeamsAdministrationFormState.ts

+10-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
/*
22
* CloudBeaver - Cloud Database Manager
3-
* Copyright (C) 2020-2024 DBeaver Corp and others
3+
* Copyright (C) 2020-2025 DBeaver Corp and others
44
*
55
* Licensed under the Apache License, Version 2.0.
66
* you may not use this file except in compliance with the License.
77
*/
8-
import { useRef } from 'react';
8+
import { useEffect, useRef } from 'react';
99

1010
import { IServiceProvider, useService } from '@cloudbeaver/core-di';
1111

@@ -18,11 +18,19 @@ export function useTeamsAdministrationFormState(id: string | null, configure?: (
1818
const ref = useRef<null | TeamsAdministrationFormState>(null);
1919

2020
if (ref.current?.state.teamId !== id) {
21+
ref.current?.dispose();
2122
ref.current = new TeamsAdministrationFormState(serviceProvider, service, {
2223
teamId: id,
2324
});
2425
configure?.(ref.current);
2526
}
2627

28+
useEffect(
29+
() => () => {
30+
ref.current?.dispose();
31+
},
32+
[],
33+
);
34+
2735
return ref.current;
2836
}

webapp/packages/plugin-authentication-administration/src/Administration/Users/Teams/TeamsTable/CreateTeamService.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*
22
* CloudBeaver - Cloud Database Manager
3-
* Copyright (C) 2020-2024 DBeaver Corp and others
3+
* Copyright (C) 2020-2025 DBeaver Corp and others
44
*
55
* Licensed under the Apache License, Version 2.0.
66
* you may not use this file except in compliance with the License.
@@ -39,6 +39,7 @@ export class CreateTeamService {
3939
}
4040

4141
fillData(): void {
42+
this.dispose();
4243
this.data = new TeamsAdministrationFormState(this.serviceProvider, this.service, {
4344
teamId: null,
4445
});
@@ -47,4 +48,9 @@ export class CreateTeamService {
4748
create(): void {
4849
this.teamsAdministrationNavService.navToCreate();
4950
}
51+
52+
dispose() {
53+
this.data?.dispose();
54+
this.data = null;
55+
}
5056
}

webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersAdministrationService.ts

+15-9
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*
22
* CloudBeaver - Cloud Database Manager
3-
* Copyright (C) 2020-2024 DBeaver Corp and others
3+
* Copyright (C) 2020-2025 DBeaver Corp and others
44
*
55
* Licensed under the Apache License, Version 2.0.
66
* you may not use this file except in compliance with the License.
@@ -59,16 +59,12 @@ export class UsersAdministrationService extends Bootstrap {
5959
},
6060
{
6161
name: EUsersAdministrationSub.Users,
62-
onDeActivate: this.cancelCreate.bind(this),
62+
onDeActivate: this.cancelUserCreate.bind(this),
6363
},
6464
{
6565
name: EUsersAdministrationSub.Teams,
6666
onActivate: this.loadTeams.bind(this),
67-
onDeActivate: (param, configurationWizard, outside) => {
68-
if (outside) {
69-
this.teamsResource.cleanNewFlags();
70-
}
71-
},
67+
onDeActivate: this.cancelTeamCreate.bind(this),
7268
},
7369
],
7470
defaultSub: EUsersAdministrationSub.Users,
@@ -78,7 +74,7 @@ export class UsersAdministrationService extends Bootstrap {
7874
this.userDetailsInfoPlaceholder.add(UserCredentialsList, 0);
7975
}
8076

81-
private async cancelCreate(param: string | null, configurationWizard: boolean, outside: boolean) {
77+
private cancelUserCreate(param: string | null, configurationWizard: boolean, outside: boolean) {
8278
if (param === 'create') {
8379
this.createUserService.close();
8480
}
@@ -88,7 +84,17 @@ export class UsersAdministrationService extends Bootstrap {
8884
}
8985
}
9086

91-
private async loadTeams(param: string | null) {
87+
private cancelTeamCreate(param: string | null, configurationWizard: boolean, outside: boolean) {
88+
if (param === 'create') {
89+
this.createTeamService.dispose();
90+
}
91+
92+
if (outside) {
93+
this.teamsResource.cleanNewFlags();
94+
}
95+
}
96+
97+
private loadTeams(param: string | null) {
9298
if (param === 'create') {
9399
this.createTeamService.fillData();
94100
}

webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/CreateUserService.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*
22
* CloudBeaver - Cloud Database Manager
3-
* Copyright (C) 2020-2024 DBeaver Corp and others
3+
* Copyright (C) 2020-2025 DBeaver Corp and others
44
*
55
* Licensed under the Apache License, Version 2.0.
66
* you may not use this file except in compliance with the License.
@@ -55,6 +55,7 @@ export class CreateUserService {
5555
}
5656

5757
clearUserTemplate(): void {
58+
this.state?.dispose();
5859
this.state = null;
5960
}
6061

webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/useAdministrationUserFormState.ts

+12-3
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
/*
22
* CloudBeaver - Cloud Database Manager
3-
* Copyright (C) 2020-2024 DBeaver Corp and others
3+
* Copyright (C) 2020-2025 DBeaver Corp and others
44
*
55
* Licensed under the Apache License, Version 2.0.
66
* you may not use this file except in compliance with the License.
77
*/
8-
import { useRef } from 'react';
8+
9+
import { useEffect, useRef } from 'react';
910

1011
import { IServiceProvider, useService } from '@cloudbeaver/core-di';
1112

@@ -17,12 +18,20 @@ export function useAdministrationUserFormState(id: string | null, configure?: (s
1718
const serviceProvider = useService(IServiceProvider);
1819
const ref = useRef<null | AdministrationUserFormState>(null);
1920

20-
if (ref.current?.id !== id) {
21+
if (ref.current?.state.userId !== id) {
22+
ref.current?.dispose();
2123
ref.current = new AdministrationUserFormState(serviceProvider, service, {
2224
userId: id,
2325
});
2426
configure?.(ref.current);
2527
}
2628

29+
useEffect(
30+
() => () => {
31+
ref.current?.dispose();
32+
},
33+
[],
34+
);
35+
2736
return ref.current;
2837
}

0 commit comments

Comments
 (0)