From 9046c01238faf226a4eb3c5d883162d7b3881bf2 Mon Sep 17 00:00:00 2001 From: Cameron Perera Date: Fri, 12 Jan 2024 14:13:57 -0600 Subject: [PATCH] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20target=20list=20will=20u?= =?UTF-8?q?pdate=20according=20to=20org=20scope=20selected=20(#2083)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 🎸 target list will update according to org scope selected ✅ Closes: https://hashicorp.atlassian.net/browse/ICU-12205 * refactor: 💡 add comment to resetController logic ✅ Closes: https://hashicorp.atlassian.net/browse/ICU-12205 * feat: 🎸 filter sessions by org selected at global level ✅ Closes: https://hashicorp.atlassian.net/browse/ICU-12205 --- .../scopes/scope/projects/sessions/index.js | 19 ++- .../scopes/scope/projects/targets/index.js | 18 ++- .../scopes/scope/projects/sessions/index.hbs | 4 +- .../scopes/scope/projects/targets/index.hbs | 4 +- .../projects/sessions/index-test.js | 110 ++++++++++++++---- .../acceptance/projects/targets/index-test.js | 45 ++++++- 6 files changed, 171 insertions(+), 29 deletions(-) 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 8d9ff7283..e0fc0f7d6 100644 --- a/ui/desktop/app/routes/scopes/scope/projects/sessions/index.js +++ b/ui/desktop/app/routes/scopes/scope/projects/sessions/index.js @@ -62,6 +62,8 @@ export default class ScopesScopeProjectsSessionsIndexRoute extends Route { */ async model({ targets, status, scopes, page, pageSize }, transition) { const from = transition.from?.name; + const projects = this.modelFor('scopes.scope.projects'); + const projectIds = projects.map((project) => project.id); const filters = { user_id: [{ equals: this.session.data.authenticated.user_id }], @@ -78,6 +80,11 @@ export default class ScopesScopeProjectsSessionsIndexRoute extends Route { scopes.forEach((scope) => { filters.scope_id.push({ equals: scope }); }); + if (scopes.length === 0) { + projectIds.forEach((projectId) => { + filters.scope_id.push({ equals: projectId }); + }); + } const queryOptions = { query: { filters }, @@ -105,8 +112,6 @@ export default class ScopesScopeProjectsSessionsIndexRoute extends Route { this.allTargets = await this.store.query('target', {}, options); } - const projects = this.modelFor('scopes.scope.projects'); - return { sessions, projects, @@ -115,4 +120,14 @@ export default class ScopesScopeProjectsSessionsIndexRoute extends Route { totalItems, }; } + + resetController(controller, isExiting, transition) { + const { to } = transition; + // Reset the scopes query param when changing org scope + if (!isExiting && to.queryParams.scopes === '[]') { + controller.setProperties({ + scopes: [], + }); + } + } } 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 90fb82518..182c0890e 100644 --- a/ui/desktop/app/routes/scopes/scope/projects/targets/index.js +++ b/ui/desktop/app/routes/scopes/scope/projects/targets/index.js @@ -84,11 +84,18 @@ export default class ScopesScopeProjectsTargetsIndexRoute extends Route { transition, ) { await this.getAllTargets(transition); + const projects = this.modelFor('scopes.scope.projects'); + const projectIds = projects.map((project) => project.id); const filters = { scope_id: [], id: { values: [] }, type: [] }; scopes.forEach((scope) => { filters.scope_id.push({ equals: scope }); }); + if (scopes.length === 0) { + projectIds.forEach((projectId) => { + filters.scope_id.push({ equals: projectId }); + }); + } types.forEach((type) => { filters.type.push({ equals: type }); }); @@ -124,7 +131,6 @@ export default class ScopesScopeProjectsTargetsIndexRoute extends Route { targets = targets.filter((target) => this.can.can('connect target', target), ); - const projects = this.modelFor('scopes.scope.projects'); return { targets, @@ -134,6 +140,16 @@ export default class ScopesScopeProjectsTargetsIndexRoute extends Route { }; } + resetController(controller, isExiting, transition) { + const { to } = transition; + // Reset the scopes query param when changing org scope + if (!isExiting && to.queryParams.scopes === '[]') { + controller.setProperties({ + scopes: [], + }); + } + } + /** * Get all the targets but only load them once when entering the route. Ideally we would lazy load it when needed * but this can be revisited in the future. 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 ca1ae794a..687f3fe5c 100644 --- a/ui/desktop/app/templates/scopes/scope/projects/sessions/index.hbs +++ b/ui/desktop/app/templates/scopes/scope/projects/sessions/index.hbs @@ -84,7 +84,7 @@ {{B.data.id}} @@ -101,7 +101,7 @@ @acknowledgeText={{t 'states.copied'}} > {{B.data.id}} {{/if}} diff --git a/ui/desktop/app/templates/scopes/scope/projects/targets/index.hbs b/ui/desktop/app/templates/scopes/scope/projects/targets/index.hbs index e31b96bc6..bc6ffb024 100644 --- a/ui/desktop/app/templates/scopes/scope/projects/targets/index.hbs +++ b/ui/desktop/app/templates/scopes/scope/projects/targets/index.hbs @@ -127,7 +127,9 @@ @buttonText={{t 'actions.copy-to-clipboard'}} @acknowledgeText={{t 'states.copied'}} > - {{B.data.project.id}} + {{B.data.project.id}} {{#if B.data.project.name}} diff --git a/ui/desktop/tests/acceptance/projects/sessions/index-test.js b/ui/desktop/tests/acceptance/projects/sessions/index-test.js index 1db0ff444..b5e8ca8eb 100644 --- a/ui/desktop/tests/acceptance/projects/sessions/index-test.js +++ b/ui/desktop/tests/acceptance/projects/sessions/index-test.js @@ -35,19 +35,25 @@ module('Acceptance | projects | sessions | index', function (hooks) { scopes: { global: null, org: null, + org2: null, project: null, + project2: null, }, authMethods: { global: null, }, user: null, + target: null, + target2: null, session: null, + session2: null, }; const urls = { scopes: { global: null, org: null, + org2: null, }, authenticate: { global: null, @@ -56,7 +62,9 @@ module('Acceptance | projects | sessions | index', function (hooks) { }, }, projects: null, + projects2: null, sessions: null, + sessions2: null, session: null, }; @@ -74,7 +82,10 @@ module('Acceptance | projects | sessions | index', function (hooks) { authenticateSession({ user_id: instances.user.id }); // create scopes - instances.scopes.global = this.server.create('scope', { id: 'global' }); + instances.scopes.global = this.server.create('scope', { + id: 'global', + name: 'Global', + }); const globalScope = { id: 'global', type: 'global' }; instances.scopes.org = this.server.create('scope', { type: 'org', @@ -85,7 +96,17 @@ module('Acceptance | projects | sessions | index', function (hooks) { type: 'project', scope: orgScope, }); + instances.scopes.org2 = this.server.create('scope', { + type: 'org', + scope: globalScope, + }); + const org2Scope = { id: instances.scopes.org2.id, type: 'org' }; + instances.scopes.project2 = this.server.create('scope', { + type: 'project', + scope: org2Scope, + }); + // create resources instances.authMethods.global = this.server.create('auth-method', { scope: instances.scopes.global, }); @@ -104,13 +125,33 @@ module('Acceptance | projects | sessions | index', function (hooks) { }, 'withAssociations', ); + // create resources in second org + instances.target2 = this.server.create( + 'target', + { scope: instances.scopes.project2 }, + 'withAssociations', + ); + instances.session2 = this.server.create( + 'session', + { + scope: instances.scopes.project2, + target: instances.target2, + status: STATUS_SESSION_ACTIVE, + user: instances.user, + }, + 'withAssociations', + ); urls.scopes.global = `/scopes/${instances.scopes.global.id}`; urls.scopes.org = `/scopes/${instances.scopes.org.id}`; + urls.scopes.org2 = `/scopes/${instances.scopes.org2.id}`; urls.authenticate.global = `${urls.scopes.global}/authenticate`; urls.authenticate.methods.global = `${urls.authenticate.global}/${instances.authMethods.global.id}`; urls.projects = `${urls.scopes.org}/projects`; + urls.projects2 = `${urls.scopes.org2}/projects`; + urls.globalSessions = `${urls.scopes.global}/projects/sessions`; urls.sessions = `${urls.projects}/sessions`; + urls.sessions2 = `${urls.projects2}/sessions`; urls.session = `${urls.projects}/sessions/${instances.session.id}`; // Mock the postMessage interface used by IPC. @@ -169,7 +210,7 @@ module('Acceptance | projects | sessions | index', function (hooks) { await visit(urls.projects); await click(`[href="${urls.sessions}"]`); - await click('[data-test-session-detail-link]'); + await click(`[data-test-session-detail-link="${instances.session.id}"]`); assert.strictEqual(currentURL(), urls.session); }); @@ -181,9 +222,7 @@ module('Acceptance | projects | sessions | index', function (hooks) { await visit(urls.sessions); assert - .dom( - 'tbody tr:first-child td:first-child [data-test-session-detail-link]', - ) + .dom(`[data-test-session-detail-link="${instances.session.id}"]`) .isVisible(); }); @@ -195,9 +234,7 @@ module('Acceptance | projects | sessions | index', function (hooks) { await click(`[href="${urls.sessions}"]`); assert - .dom( - 'tbody tr:first-child td:first-child [data-test-session-detail-link]', - ) + .dom(`[data-test-session-detail-link="${instances.session.id}"]`) .isVisible(); }); @@ -209,9 +246,7 @@ module('Acceptance | projects | sessions | index', function (hooks) { await click(`[href="${urls.sessions}"]`); assert - .dom( - 'tbody tr:first-child td:first-child [data-test-session-detail-link]', - ) + .dom(`[data-test-session-detail-link="${instances.session.id}"]`) .isVisible(); }); @@ -223,9 +258,7 @@ module('Acceptance | projects | sessions | index', function (hooks) { await click(`[href="${urls.sessions}"]`); assert - .dom( - 'tbody tr:first-child td:first-child [data-test-session-detail-link]', - ) + .dom(`[data-test-session-detail-link="${instances.session.id}"]`) .isVisible(); }); @@ -235,12 +268,10 @@ module('Acceptance | projects | sessions | index', function (hooks) { await visit(urls.sessions); assert - .dom( - 'tbody tr:first-child td:first-child [data-test-session-detail-link]', - ) + .dom(`[data-test-session-detail-link="${instances.session.id}"]`) .doesNotExist(); assert - .dom('tbody tr:first-child td:first-child [data-test-session-id-copy]') + .dom(`[data-test-session-id-copy="${instances.session.id}"]`) .isVisible(); }); @@ -250,12 +281,10 @@ module('Acceptance | projects | sessions | index', function (hooks) { await visit(urls.sessions); assert - .dom( - 'tbody tr:first-child td:first-child [data-test-session-detail-link]', - ) + .dom(`[data-test-session-detail-link="${instances.session.id}"]`) .doesNotExist(); assert - .dom('tbody tr:first-child td:first-child [data-test-session-id-copy]') + .dom(`[data-test-session-id-copy="${instances.session.id}"]`) .isVisible(); }); @@ -332,4 +361,41 @@ module('Acceptance | projects | sessions | index', function (hooks) { assert.dom('[role="alert"].is-error').isVisible(); }); + + test('user can change org scope and only sessions for that org will be displayed', async function (assert) { + this.stubClientDaemonSearch( + 'sessions', + 'sessions', + 'targets', + 'targets', + 'sessions', + 'targets', + { + resource: 'sessions', + func: () => [instances.session2], + }, + 'sessions', + 'targets', + ); + await visit(urls.globalSessions); + + assert + .dom(`[data-test-session-detail-link="${instances.session.id}"]`) + .isVisible(); + assert + .dom(`[data-test-session-detail-link="${instances.session2.id}"]`) + .isVisible(); + + // change scope in app header + await click('.rose-header-nav .rose-dropdown a:nth-of-type(3)'); + // navigate to back sessions + await click(`[href="${urls.sessions2}"]`); + + assert + .dom(`[data-test-session-detail-link="${instances.session.id}"]`) + .doesNotExist(); + assert + .dom(`[data-test-session-detail-link="${instances.session2.id}"]`) + .isVisible(); + }); }); diff --git a/ui/desktop/tests/acceptance/projects/targets/index-test.js b/ui/desktop/tests/acceptance/projects/targets/index-test.js index e7c6ecb4a..5d14f2b6e 100644 --- a/ui/desktop/tests/acceptance/projects/targets/index-test.js +++ b/ui/desktop/tests/acceptance/projects/targets/index-test.js @@ -47,12 +47,15 @@ module('Acceptance | projects | targets | index', function (hooks) { scopes: { global: null, org: null, + org2: null, project: null, + project2: null, }, authMethods: { global: null, }, target: null, + target2: null, session: null, }; @@ -85,7 +88,10 @@ module('Acceptance | projects | targets | index', function (hooks) { this.server.get('/targets', ({ targets }) => targets.all()); // Generate scopes - instances.scopes.global = this.server.create('scope', { id: 'global' }); + instances.scopes.global = this.server.create('scope', { + id: 'global', + name: 'Global', + }); const globalScope = { id: 'global', type: 'global' }; instances.scopes.org = this.server.create('scope', { type: 'org', @@ -96,6 +102,15 @@ module('Acceptance | projects | targets | index', function (hooks) { type: 'project', scope: orgScope, }); + instances.scopes.org2 = this.server.create('scope', { + type: 'org', + scope: globalScope, + }); + const org2Scope = { id: instances.scopes.org2.id, type: 'org' }; + instances.scopes.project2 = this.server.create('scope', { + type: 'project', + scope: org2Scope, + }); // Generate resources instances.authMethods.global = this.server.create('auth-method', { @@ -119,6 +134,10 @@ module('Acceptance | projects | targets | index', function (hooks) { }, 'withAssociations', ); + instances.target2 = this.server.create('target', { + scope: instances.scopes.project2, + address: 'localhost', + }); // Generate route URLs for resources urls.scopes.global = `/scopes/${instances.scopes.global.id}`; @@ -412,4 +431,28 @@ module('Acceptance | projects | targets | index', function (hooks) { assert.strictEqual(currentURL(), urls.session); }); + + test('user can change org scope and only targets for that org will be displayed', async function (assert) { + this.stubClientDaemonSearch('targets', 'sessions', 'targets', 'sessions', { + resource: 'targets', + func: () => [instances.target], + }); + + await visit(urls.scopes.global); + + assert + .dom(`[data-test-target-project-id="${instances.scopes.project.id}"]`) + .exists(); + assert + .dom(`[data-test-target-project-id="${instances.scopes.project2.id}"]`) + .exists(); + + await click('.rose-header-nav .rose-dropdown a:nth-of-type(2)'); + assert + .dom(`[data-test-target-project-id="${instances.scopes.project.id}"]`) + .exists(); + assert + .dom(`[data-test-target-project-id="${instances.scopes.project2.id}"]`) + .doesNotExist(); + }); });