diff --git a/addons/api/addon/handlers/client-daemon-handler.js b/addons/api/addon/handlers/client-daemon-handler.js index 95d7563d5..4d948454e 100644 --- a/addons/api/addon/handlers/client-daemon-handler.js +++ b/addons/api/addon/handlers/client-daemon-handler.js @@ -39,6 +39,21 @@ const paginateResults = (array, page, pageSize) => { return array.slice(start, end); }; +const fetchControllerData = async (context, next) => { + const { data } = context.request; + const { query } = data; + const { recursive, scope_id, page, pageSize } = query; + + // If we get an error or client daemon is unavailable, fall back to calling the API + context.request.data.query = { recursive, scope_id }; + const results = await next(context.request); + const models = results.content.toArray(); + + const paginatedResult = paginateResults(models, page, pageSize); + paginatedResult.meta = { totalItems: models.length }; + return paginatedResult; +}; + /** * Handler to sit in front of the API request layer * so we can request from the daemon first @@ -60,18 +75,17 @@ export default class ClientDaemonHandler { 'isClientDaemonRunning', ); + // eslint-disable-next-line no-unused-vars + let { recursive, scope_id, page, pageSize, ...remainingQuery } = query; + let searchQuery = ''; + if ( !Object.keys(supportedTypes).includes(type) || !isClientDaemonRunning ) { - return next(context.request); + return fetchControllerData(context, next); } - // TODO: Remove usages of recursive from callers and scope_id since - // all calls to daemon are recursive, scope_id can be part of search query instead - // eslint-disable-next-line no-unused-vars - let { recursive, scope_id, page, pageSize, ...remainingQuery } = query; - let searchQuery = ''; if (remainingQuery.query) { const { search, filters } = remainingQuery.query; @@ -91,12 +105,20 @@ export default class ClientDaemonHandler { resource: pluralize(type), }; + let clientDaemonResults = {}; + try { + clientDaemonResults = await this.ipc.invoke( + 'searchClientDaemon', + remainingQuery, + ); + } catch (e) { + return fetchControllerData(context, next); + } + // Currently returns with a singular top level field with resource name // e.g. { targets: [...] } or { sessions: [..] } // So this just unwraps to the array, or undefined - const [results] = Object.values( - await this.ipc.invoke('searchClientDaemon', remainingQuery), - ); + const [results] = Object.values(clientDaemonResults); const payload = { items: paginateResults(results, page, pageSize) }; const schema = store.modelFor(type); diff --git a/ui/desktop/app/routes/scopes/scope/projects/sessions/index.js b/ui/desktop/app/routes/scopes/scope/projects/sessions/index.js index e0fc0f7d6..9c0b1fe74 100644 --- a/ui/desktop/app/routes/scopes/scope/projects/sessions/index.js +++ b/ui/desktop/app/routes/scopes/scope/projects/sessions/index.js @@ -12,6 +12,7 @@ export default class ScopesScopeProjectsSessionsIndexRoute extends Route { @service session; @service store; + @service ipc; // =attributes @@ -64,6 +65,7 @@ export default class ScopesScopeProjectsSessionsIndexRoute extends Route { const from = transition.from?.name; const projects = this.modelFor('scopes.scope.projects'); const projectIds = projects.map((project) => project.id); + const { id: scope_id } = this.modelFor('scopes.scope'); const filters = { user_id: [{ equals: this.session.data.authenticated.user_id }], @@ -87,6 +89,8 @@ export default class ScopesScopeProjectsSessionsIndexRoute extends Route { } const queryOptions = { + scope_id, + recursive: true, query: { filters }, page, pageSize, @@ -101,6 +105,8 @@ export default class ScopesScopeProjectsSessionsIndexRoute extends Route { this.allSessions = await this.store.query( 'session', { + scope_id, + recursive: true, query: { filters: { user_id: [{ equals: this.session.data.authenticated.user_id }], @@ -109,7 +115,11 @@ export default class ScopesScopeProjectsSessionsIndexRoute extends Route { }, options, ); - this.allTargets = await this.store.query('target', {}, options); + this.allTargets = await this.store.query( + 'target', + { scope_id, recursive: true }, + options, + ); } return { @@ -118,6 +128,7 @@ export default class ScopesScopeProjectsSessionsIndexRoute extends Route { allSessions: this.allSessions, allTargets: this.allTargets, totalItems, + isClientDaemonRunning: await this.ipc.invoke('isClientDaemonRunning'), }; } diff --git a/ui/desktop/app/routes/scopes/scope/projects/targets/index.js b/ui/desktop/app/routes/scopes/scope/projects/targets/index.js index 182c0890e..d3907c97d 100644 --- a/ui/desktop/app/routes/scopes/scope/projects/targets/index.js +++ b/ui/desktop/app/routes/scopes/scope/projects/targets/index.js @@ -20,6 +20,7 @@ export default class ScopesScopeProjectsTargetsIndexRoute extends Route { @service router; @service session; @service store; + @service ipc; // =attributes @@ -83,7 +84,8 @@ export default class ScopesScopeProjectsTargetsIndexRoute extends Route { { search, scopes, availableSessions, types, page, pageSize }, transition, ) { - await this.getAllTargets(transition); + const { id: scope_id } = this.modelFor('scopes.scope'); + await this.getAllTargets(transition, scope_id); const projects = this.modelFor('scopes.scope.projects'); const projectIds = projects.map((project) => project.id); @@ -103,6 +105,8 @@ export default class ScopesScopeProjectsTargetsIndexRoute extends Route { // Retrieve all sessions so that the session and activeSessions getters // in the target model always retrieve the most up-to-date sessions. const sessions = await this.store.query('session', { + recursive: true, + scope_id, query: { filters: { user_id: [{ equals: this.session.data.authenticated.user_id }], @@ -118,6 +122,8 @@ export default class ScopesScopeProjectsTargetsIndexRoute extends Route { this.addActiveSessionFilters(filters, availableSessions, sessions); let targets = await this.store.query('target', { + recursive: true, + scope_id, query: { search, filters }, page, pageSize, @@ -125,8 +131,6 @@ export default class ScopesScopeProjectsTargetsIndexRoute extends Route { }); const totalItems = targets.meta?.totalItems; - // TODO: Filter targets by scope we're in manually - // const { id: scope_id } = this.modelFor('scopes.scope'); // Filter out targets to which users do not have the connect ability targets = targets.filter((target) => this.can.can('connect target', target), @@ -137,6 +141,7 @@ export default class ScopesScopeProjectsTargetsIndexRoute extends Route { projects, allTargets: this.allTargets, totalItems, + isClientDaemonRunning: await this.ipc.invoke('isClientDaemonRunning'), }; } @@ -156,13 +161,17 @@ export default class ScopesScopeProjectsTargetsIndexRoute extends Route { * @param transition * @returns {Promise} */ - async getAllTargets(transition) { + async getAllTargets(transition, scope_id) { const from = transition.from?.name; // Query all targets for defining filtering values if entering route for first time if (from !== 'scopes.scope.projects.targets.index') { const options = { pushToStore: false }; - const allTargets = await this.store.query('target', {}, options); + const allTargets = await this.store.query( + 'target', + { scope_id, recursive: true }, + options, + ); // Filter out targets to which users do not have the connect ability this.allTargets = allTargets.filter((target) => diff --git a/ui/desktop/app/templates/scopes/scope/projects/sessions/index.hbs b/ui/desktop/app/templates/scopes/scope/projects/sessions/index.hbs index 687f3fe5c..e65665b26 100644 --- a/ui/desktop/app/templates/scopes/scope/projects/sessions/index.hbs +++ b/ui/desktop/app/templates/scopes/scope/projects/sessions/index.hbs @@ -4,55 +4,57 @@ }} {{#if (or @model.sessions @model.allSessions)}} - - - - - - - - - - {{#let (group-by itemOptions 'scopeModel') as |orgs|}} - {{#each orgs as |org|}} - - - {{org.key.displayName}} - - {{#each org.items as |project|}} - - {{project.displayName}} - + {{#if @model.isClientDaemonRunning}} + + + + + + + + + + {{#let (group-by itemOptions 'scopeModel') as |orgs|}} + {{#each orgs as |org|}} + + + {{org.key.displayName}} + + {{#each org.items as |project|}} + + {{project.displayName}} + + {{/each}} {{/each}} - {{/each}} - {{/let}} - - - - + {{/let}} + + + + + {{/if}} {{#if @model.sessions}} - - - + - - - - - - - {{#let (group-by itemOptions 'scopeModel') as |orgs|}} - {{#each orgs as |org|}} - - - {{org.key.displayName}} - - {{#each org.items as |project|}} - - {{project.displayName}} - + + + + + + + + + {{#let (group-by itemOptions 'scopeModel') as |orgs|}} + {{#each orgs as |org|}} + + + {{org.key.displayName}} + + {{#each org.items as |project|}} + + {{project.displayName}} + + {{/each}} {{/each}} - {{/each}} - {{/let}} - - - - + {{/let}} + + + + + {{/if}} + {{#if @model.targets}} * Check to see if the client daemon is running. We use the presence of a * socket path as a proxy for whether the daemon is running. */ -handle('isClientDaemonRunning', async () => - Boolean(clientDaemonManager.socketPath), -); +handle('isClientDaemonRunning', async () => { + clientDaemonManager.status(); + return Boolean(clientDaemonManager.socketPath); +}); /** * Handler to help create terminal windows. We don't use the helper `handle` method diff --git a/ui/desktop/tests/acceptance/projects/sessions/index-test.js b/ui/desktop/tests/acceptance/projects/sessions/index-test.js index b5e8ca8eb..57b842a9c 100644 --- a/ui/desktop/tests/acceptance/projects/sessions/index-test.js +++ b/ui/desktop/tests/acceptance/projects/sessions/index-test.js @@ -180,6 +180,7 @@ module('Acceptance | projects | sessions | index', function (hooks) { await click(`[href="${urls.sessions}"]`); assert.strictEqual(currentURL(), urls.sessions); + assert.dom('.hds-segmented-group').exists(); assert.dom('tbody tr').exists({ count: sessionsCount }); }); @@ -398,4 +399,16 @@ module('Acceptance | projects | sessions | index', function (hooks) { .dom(`[data-test-session-detail-link="${instances.session2.id}"]`) .isVisible(); }); + + test('sessions list view still loads with no client daemon', async function (assert) { + this.ipcStub.withArgs('isClientDaemonRunning').returns(false); + this.stubClientDaemonSearch(); + const sessionsCount = this.server.schema.sessions.all().models.length; + + await visit(urls.globalSessions); + + assert.dom('.hds-segmented-group').doesNotExist(); + assert.strictEqual(currentURL(), urls.globalSessions); + assert.dom('tbody tr').exists({ count: sessionsCount }); + }); }); diff --git a/ui/desktop/tests/acceptance/projects/targets/index-test.js b/ui/desktop/tests/acceptance/projects/targets/index-test.js index 5d14f2b6e..3f59651f8 100644 --- a/ui/desktop/tests/acceptance/projects/targets/index-test.js +++ b/ui/desktop/tests/acceptance/projects/targets/index-test.js @@ -177,6 +177,7 @@ module('Acceptance | projects | targets | index', function (hooks) { await click(`[href="${urls.targets}"]`); await a11yAudit(); + assert.dom('.hds-segmented-group').exists(); assert.strictEqual(currentURL(), urls.targets); assert.strictEqual(getTargetCount(), targetsCount); }); @@ -455,4 +456,18 @@ module('Acceptance | projects | targets | index', function (hooks) { .dom(`[data-test-target-project-id="${instances.scopes.project2.id}"]`) .doesNotExist(); }); + + test('targets list view still loads with no client daemon', async function (assert) { + this.ipcStub.withArgs('isClientDaemonRunning').returns(false); + this.stubClientDaemonSearch(); + const targetsCount = getTargetCount(); + + await visit(urls.projects); + + await click(`[href="${urls.targets}"]`); + + assert.dom('.hds-segmented-group').doesNotExist(); + assert.strictEqual(currentURL(), urls.targets); + assert.dom('tbody tr').exists({ count: targetsCount }); + }); });