Skip to content

Commit 7becccd

Browse files
authored
fix: πŸ› Fix search field race condition in DC targets (#2723)
* fix: πŸ› Fix search field race condition in DC targets * feat: 🎸 Keep track of last search to know when to debounce βœ… Closes: ICU-14775
1 parent dff5cac commit 7becccd

File tree

2 files changed

+86
-64
lines changed
  • ui/desktop/app
    • controllers/scopes/scope/projects/targets
    • routes/scopes/scope/projects/targets

2 files changed

+86
-64
lines changed

β€Žui/desktop/app/controllers/scopes/scope/projects/targets/index.js

-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import { service } from '@ember/service';
88
import { action } from '@ember/object';
99
import { loading } from 'ember-loading';
1010
import { tracked } from '@glimmer/tracking';
11-
import { debounce } from 'core/decorators/debounce';
1211
import { notifySuccess, notifyError } from 'core/decorators/notify';
1312
import orderBy from 'lodash/orderBy';
1413
import { TYPES_TARGET } from 'api/models/target';
@@ -251,7 +250,6 @@ export default class ScopesScopeProjectsTargetsIndexController extends Controlle
251250
* @param {object} event
252251
*/
253252
@action
254-
@debounce(250)
255253
handleSearchInput(event) {
256254
const { value } = event.target;
257255
this.search = value;

β€Žui/desktop/app/routes/scopes/scope/projects/targets/index.js

+86-62
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
STATUS_SESSION_PENDING,
1111
} from 'api/models/session';
1212
import { action } from '@ember/object';
13+
import { restartableTask, timeout } from 'ember-concurrency';
1314

1415
const { __electronLog } = globalThis;
1516

@@ -82,78 +83,101 @@ export default class ScopesScopeProjectsTargetsIndexRoute extends Route {
8283
* @return {Promise<{totalItems: number, targets: [TargetModel], projects: [ScopeModel],
8384
* isCacheDaemonRunning: boolean, isLoadIncomplete: boolean, isCacheRefreshing: boolean}>}
8485
*/
85-
async model({ search, scopes, availableSessions, types, page, pageSize }) {
86-
const isCacheRunningPromise = this.ipc.invoke('isCacheDaemonRunning');
86+
async model(params) {
87+
const useDebounce =
88+
this.retrieveData?.lastPerformed?.args?.[0].search !== params.search;
89+
return this.retrieveData.perform({ ...params, useDebounce });
90+
}
8791

88-
const orgScope = this.modelFor('scopes.scope');
89-
const projects = this.modelFor('scopes.scope.projects');
92+
retrieveData = restartableTask(
93+
async ({
94+
search,
95+
scopes,
96+
availableSessions,
97+
types,
98+
page,
99+
pageSize,
100+
useDebounce,
101+
}) => {
102+
if (useDebounce) {
103+
await timeout(250);
104+
}
90105

91-
// orgFilter used to narrow down resources to only those under
92-
// the current org scope if org is not global
93-
const orgFilter = `"/item/scope/parent_scope_id" == "${orgScope.id}"`;
106+
const isCacheRunningPromise = this.ipc.invoke('isCacheDaemonRunning');
94107

95-
const filters = { scope_id: [], id: { values: [] }, type: [] };
96-
scopes.forEach((scope) => {
97-
filters.scope_id.push({ equals: scope });
98-
});
99-
types.forEach((type) => {
100-
filters.type.push({ equals: type });
101-
});
108+
const orgScope = this.modelFor('scopes.scope');
109+
const projects = this.modelFor('scopes.scope.projects');
102110

103-
const sessions = await this.getSessions(orgScope, scopes, orgFilter);
104-
this.addActiveSessionFilters(filters, availableSessions, sessions);
111+
// orgFilter used to narrow down resources to only those under
112+
// the current org scope if org is not global
113+
const orgFilter = `"/item/scope/parent_scope_id" == "${orgScope.id}"`;
105114

106-
const query = {
107-
recursive: true,
108-
scope_id: orgScope.id,
109-
query: { search, filters },
110-
page,
111-
pageSize,
112-
force_refresh: true,
113-
};
114-
if (orgScope.isOrg && scopes.length === 0) {
115-
query.filter = orgFilter;
116-
}
117-
let targets = await this.store.query('target', query);
118-
const { totalItems, isLoadIncomplete, isCacheRefreshing } = targets.meta;
119-
// Filter out targets to which users do not have the connect ability
120-
targets = targets.filter((target) =>
121-
this.can.can('connect target', target),
122-
);
115+
const filters = { scope_id: [], id: { values: [] }, type: [] };
116+
scopes.forEach((scope) => {
117+
filters.scope_id.push({ equals: scope });
118+
});
119+
types.forEach((type) => {
120+
filters.type.push({ equals: type });
121+
});
123122

124-
const aliasPromise = this.store.query('alias', {
125-
scope_id: 'global',
126-
force_refresh: true,
127-
query: {
128-
filters: {
129-
destination_id: {
130-
logicalOperator: 'or',
131-
values: targets.map((target) => ({
132-
equals: target.id,
133-
})),
123+
const sessions = await this.getSessions(orgScope, scopes, orgFilter);
124+
this.addActiveSessionFilters(filters, availableSessions, sessions);
125+
126+
const query = {
127+
recursive: true,
128+
scope_id: orgScope.id,
129+
query: { search, filters },
130+
page,
131+
pageSize,
132+
force_refresh: true,
133+
};
134+
if (orgScope.isOrg && scopes.length === 0) {
135+
query.filter = orgFilter;
136+
}
137+
let targets = await this.store.query('target', query);
138+
const { totalItems, isLoadIncomplete, isCacheRefreshing } = targets.meta;
139+
// Filter out targets to which users do not have the connect ability
140+
targets = targets.filter((target) =>
141+
this.can.can('connect target', target),
142+
);
143+
144+
const aliasPromise = this.store.query('alias', {
145+
scope_id: 'global',
146+
force_refresh: true,
147+
query: {
148+
filters: {
149+
destination_id: {
150+
logicalOperator: 'or',
151+
values: targets.map((target) => ({
152+
equals: target.id,
153+
})),
154+
},
134155
},
135156
},
136-
},
137-
});
157+
});
138158

139-
// Load the aliases for the targets on the current page
140-
try {
141-
await aliasPromise;
142-
} catch (e) {
143-
__electronLog?.warn('Could not retrieve aliases for targets', e.message);
144-
// Separately await and catch the error here so we can continue loading
145-
// the page in case the controller doesn't support aliases yet
146-
}
159+
// Load the aliases for the targets on the current page
160+
try {
161+
await aliasPromise;
162+
} catch (e) {
163+
__electronLog?.warn(
164+
'Could not retrieve aliases for targets',
165+
e.message,
166+
);
167+
// Separately await and catch the error here so we can continue loading
168+
// the page in case the controller doesn't support aliases yet
169+
}
147170

148-
return {
149-
targets,
150-
projects,
151-
totalItems,
152-
isLoadIncomplete,
153-
isCacheRefreshing,
154-
isCacheDaemonRunning: await isCacheRunningPromise,
155-
};
156-
}
171+
return {
172+
targets,
173+
projects,
174+
totalItems,
175+
isLoadIncomplete,
176+
isCacheRefreshing,
177+
isCacheDaemonRunning: await isCacheRunningPromise,
178+
};
179+
},
180+
);
157181

158182
async getSessions(orgScope, scopes, orgFilter) {
159183
// Retrieve all sessions so that the session and activeSessions getters

0 commit comments

Comments
Β (0)