feat: 🎸 Add fallback if client daemon is down or errors (#2085)
* feat: 🎸 Add fallback if client daemon is down or errors * test: 💍 Add tests for client daemon being down ✅ Closes: https://hashicorp.atlassian.net/browse/ICU-12026
This commit is contained in:
1 parent
9046c01238
commit
2e36d3c3e2
8 files changed
+195
-119
No files matched your search
@@ -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'),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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<void>}
|
||||
*/
|
||||
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) =>
|
||||
|
||||
@@ -4,55 +4,57 @@
|
||||
}}
|
||||
|
||||
{{#if (or @model.sessions @model.allSessions)}}
|
||||
<Hds::SegmentedGroup as |S|>
|
||||
<S.Generic>
|
||||
<Dropdown
|
||||
@name={{t 'resources.target.title'}}
|
||||
@itemOptions={{this.availableTargets}}
|
||||
@checkedItems={{this.targets}}
|
||||
@applyFilter={{fn this.applyFilter 'targets'}}
|
||||
@isSearchable={{true}}
|
||||
@listPosition='bottom-left'
|
||||
/>
|
||||
</S.Generic>
|
||||
<S.Generic>
|
||||
<Dropdown
|
||||
@name={{t 'form.status.label'}}
|
||||
@itemOptions={{this.sessionStatusOptions}}
|
||||
@checkedItems={{this.status}}
|
||||
@applyFilter={{fn this.applyFilter 'status'}}
|
||||
/>
|
||||
</S.Generic>
|
||||
<S.Generic>
|
||||
<Dropdown
|
||||
@name={{t 'resources.scope.title'}}
|
||||
@itemOptions={{this.availableScopes}}
|
||||
@checkedItems={{this.scopes}}
|
||||
@applyFilter={{fn this.applyFilter 'scopes'}}
|
||||
@isSearchable={{true}}
|
||||
as |FD selectItem itemOptions|
|
||||
>
|
||||
{{#let (group-by itemOptions 'scopeModel') as |orgs|}}
|
||||
{{#each orgs as |org|}}
|
||||
<FD.Generic>
|
||||
<FlightIcon @name='org' />
|
||||
{{org.key.displayName}}
|
||||
</FD.Generic>
|
||||
{{#each org.items as |project|}}
|
||||
<FD.Checkbox
|
||||
@value={{project.id}}
|
||||
checked={{includes project.id this.scopes}}
|
||||
{{on 'change' selectItem}}
|
||||
>
|
||||
{{project.displayName}}
|
||||
</FD.Checkbox>
|
||||
{{#if @model.isClientDaemonRunning}}
|
||||
<Hds::SegmentedGroup as |S|>
|
||||
<S.Generic>
|
||||
<Dropdown
|
||||
@name={{t 'resources.target.title'}}
|
||||
@itemOptions={{this.availableTargets}}
|
||||
@checkedItems={{this.targets}}
|
||||
@applyFilter={{fn this.applyFilter 'targets'}}
|
||||
@isSearchable={{true}}
|
||||
@listPosition='bottom-left'
|
||||
/>
|
||||
</S.Generic>
|
||||
<S.Generic>
|
||||
<Dropdown
|
||||
@name={{t 'form.status.label'}}
|
||||
@itemOptions={{this.sessionStatusOptions}}
|
||||
@checkedItems={{this.status}}
|
||||
@applyFilter={{fn this.applyFilter 'status'}}
|
||||
/>
|
||||
</S.Generic>
|
||||
<S.Generic>
|
||||
<Dropdown
|
||||
@name={{t 'resources.scope.title'}}
|
||||
@itemOptions={{this.availableScopes}}
|
||||
@checkedItems={{this.scopes}}
|
||||
@applyFilter={{fn this.applyFilter 'scopes'}}
|
||||
@isSearchable={{true}}
|
||||
as |FD selectItem itemOptions|
|
||||
>
|
||||
{{#let (group-by itemOptions 'scopeModel') as |orgs|}}
|
||||
{{#each orgs as |org|}}
|
||||
<FD.Generic>
|
||||
<FlightIcon @name='org' />
|
||||
{{org.key.displayName}}
|
||||
</FD.Generic>
|
||||
{{#each org.items as |project|}}
|
||||
<FD.Checkbox
|
||||
@value={{project.id}}
|
||||
checked={{includes project.id this.scopes}}
|
||||
{{on 'change' selectItem}}
|
||||
>
|
||||
{{project.displayName}}
|
||||
</FD.Checkbox>
|
||||
{{/each}}
|
||||
{{/each}}
|
||||
{{/each}}
|
||||
{{/let}}
|
||||
</Dropdown>
|
||||
</S.Generic>
|
||||
</Hds::SegmentedGroup>
|
||||
<FilterTags @filters={{this.filters}} />
|
||||
{{/let}}
|
||||
</Dropdown>
|
||||
</S.Generic>
|
||||
</Hds::SegmentedGroup>
|
||||
<FilterTags @filters={{this.filters}} />
|
||||
{{/if}}
|
||||
{{#if @model.sessions}}
|
||||
<Hds::Table
|
||||
@model={{this.sortedSessions}}
|
||||
|
||||
@@ -4,61 +4,64 @@
|
||||
}}
|
||||
|
||||
{{#if this.showFilters}}
|
||||
<Hds::SegmentedGroup as |S|>
|
||||
<S.TextInput
|
||||
@value={{this.search}}
|
||||
@type='search'
|
||||
placeholder={{t 'actions.search'}}
|
||||
aria-label={{t 'actions.search'}}
|
||||
{{on 'input' this.handleSearchInput}}
|
||||
/>
|
||||
<S.Generic>
|
||||
<Dropdown
|
||||
@name={{t 'resources.target.titles.active-sessions'}}
|
||||
@itemOptions={{this.availableSessionOptions}}
|
||||
@checkedItems={{this.availableSessions}}
|
||||
@applyFilter={{fn this.applyFilter 'availableSessions'}}
|
||||
{{#if @model.isClientDaemonRunning}}
|
||||
<Hds::SegmentedGroup as |S|>
|
||||
<S.TextInput
|
||||
@value={{this.search}}
|
||||
@type='search'
|
||||
placeholder={{t 'actions.search'}}
|
||||
aria-label={{t 'actions.search'}}
|
||||
{{on 'input' this.handleSearchInput}}
|
||||
/>
|
||||
</S.Generic>
|
||||
<S.Generic>
|
||||
<Dropdown
|
||||
@name={{t 'form.type.label'}}
|
||||
@itemOptions={{this.targetTypeOptions}}
|
||||
@checkedItems={{this.types}}
|
||||
@applyFilter={{fn this.applyFilter 'types'}}
|
||||
/>
|
||||
</S.Generic>
|
||||
<S.Generic>
|
||||
<Dropdown
|
||||
@name={{t 'resources.scope.title'}}
|
||||
@itemOptions={{this.availableScopes}}
|
||||
@checkedItems={{this.scopes}}
|
||||
@applyFilter={{fn this.applyFilter 'scopes'}}
|
||||
@isSearchable={{true}}
|
||||
as |FD selectItem itemOptions|
|
||||
>
|
||||
{{#let (group-by itemOptions 'scopeModel') as |orgs|}}
|
||||
{{#each orgs as |org|}}
|
||||
<FD.Generic>
|
||||
<FlightIcon @name='org' />
|
||||
{{org.key.displayName}}
|
||||
</FD.Generic>
|
||||
{{#each org.items as |project|}}
|
||||
<FD.Checkbox
|
||||
@value={{project.id}}
|
||||
data-test-checkbox={{project.name}}
|
||||
checked={{includes project.id this.scopes}}
|
||||
{{on 'change' selectItem}}
|
||||
>
|
||||
{{project.displayName}}
|
||||
</FD.Checkbox>
|
||||
<S.Generic>
|
||||
<Dropdown
|
||||
@name={{t 'resources.target.titles.active-sessions'}}
|
||||
@itemOptions={{this.availableSessionOptions}}
|
||||
@checkedItems={{this.availableSessions}}
|
||||
@applyFilter={{fn this.applyFilter 'availableSessions'}}
|
||||
/>
|
||||
</S.Generic>
|
||||
<S.Generic>
|
||||
<Dropdown
|
||||
@name={{t 'form.type.label'}}
|
||||
@itemOptions={{this.targetTypeOptions}}
|
||||
@checkedItems={{this.types}}
|
||||
@applyFilter={{fn this.applyFilter 'types'}}
|
||||
/>
|
||||
</S.Generic>
|
||||
<S.Generic>
|
||||
<Dropdown
|
||||
@name={{t 'resources.scope.title'}}
|
||||
@itemOptions={{this.availableScopes}}
|
||||
@checkedItems={{this.scopes}}
|
||||
@applyFilter={{fn this.applyFilter 'scopes'}}
|
||||
@isSearchable={{true}}
|
||||
as |FD selectItem itemOptions|
|
||||
>
|
||||
{{#let (group-by itemOptions 'scopeModel') as |orgs|}}
|
||||
{{#each orgs as |org|}}
|
||||
<FD.Generic>
|
||||
<FlightIcon @name='org' />
|
||||
{{org.key.displayName}}
|
||||
</FD.Generic>
|
||||
{{#each org.items as |project|}}
|
||||
<FD.Checkbox
|
||||
@value={{project.id}}
|
||||
data-test-checkbox={{project.name}}
|
||||
checked={{includes project.id this.scopes}}
|
||||
{{on 'change' selectItem}}
|
||||
>
|
||||
{{project.displayName}}
|
||||
</FD.Checkbox>
|
||||
{{/each}}
|
||||
{{/each}}
|
||||
{{/each}}
|
||||
{{/let}}
|
||||
</Dropdown>
|
||||
</S.Generic>
|
||||
</Hds::SegmentedGroup>
|
||||
<FilterTags @filters={{this.filters}} />
|
||||
{{/let}}
|
||||
</Dropdown>
|
||||
</S.Generic>
|
||||
</Hds::SegmentedGroup>
|
||||
<FilterTags @filters={{this.filters}} />
|
||||
{{/if}}
|
||||
|
||||
{{#if @model.targets}}
|
||||
<Hds::Table
|
||||
@model={{@model.targets}}
|
||||
|
||||
@@ -146,9 +146,10 @@ handle('searchClientDaemon', async (request) =>
|
||||
* 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
|
||||
|
||||
@@ -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 });
|
||||
});
|
||||
});
|
||||
@@ -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 });
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user