From c7483e5602b4a53a33bdc86425f1c590185989de Mon Sep 17 00:00:00 2001
From: Lisbet Alvarez <107949262+lisbet-alvarez@users.noreply.github.com>
Date: Fri, 22 Dec 2023 09:56:07 -0800
Subject: [PATCH] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20filter=20sessions=20by?=
=?UTF-8?q?=20status=20&=20scope=20(#2058)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* feat: 🎸 filter sessions by status
✅ Closes: https://hashicorp.atlassian.net/browse/ICU-12067
---
addons/api/addon/models/target.js | 8 +++
.../core/addon/components/dropdown/index.js | 4 +-
addons/core/translations/resources/en-us.yaml | 1 +
.../scopes/scope/projects/sessions/index.js | 42 ++++++++++++---
.../scopes/scope/projects/targets/index.js | 36 +++++++++++--
.../scopes/scope/projects/sessions/index.js | 47 ++++++++++++----
.../scopes/scope/projects/sessions/index.hbs | 53 ++++++++++++++-----
.../scopes/scope/projects/targets/index.hbs | 9 ++++
8 files changed, 163 insertions(+), 37 deletions(-)
diff --git a/addons/api/addon/models/target.js b/addons/api/addon/models/target.js
index a9db38c7b..0068d8497 100644
--- a/addons/api/addon/models/target.js
+++ b/addons/api/addon/models/target.js
@@ -108,6 +108,14 @@ export default class TargetModel extends GeneratedTargetModel {
.filter((s) => s && s.target_id === this.id);
}
+ /**
+ * Available sessions associated with this target (but only already loaded sessions).
+ * @type {SessionModel[]}
+ */
+ get availableSessions() {
+ return this.sessions.filter((session) => session.isAvailable);
+ }
+
/**
* The project associated with this target (if already loaded).
* @type {ScopeModel}
diff --git a/addons/core/addon/components/dropdown/index.js b/addons/core/addon/components/dropdown/index.js
index 779d90e27..e6a9643a6 100644
--- a/addons/core/addon/components/dropdown/index.js
+++ b/addons/core/addon/components/dropdown/index.js
@@ -54,9 +54,7 @@ export default class DropdownComponent extends Component {
if (checked) {
this.selectedItems = [...this.selectedItems, value];
} else {
- this.selectedItems = this.selectedItems.filter(
- (scope) => scope !== value,
- );
+ this.selectedItems = this.selectedItems.filter((item) => item !== value);
}
}
diff --git a/addons/core/translations/resources/en-us.yaml b/addons/core/translations/resources/en-us.yaml
index 691b9fd36..17dd1b9f1 100644
--- a/addons/core/translations/resources/en-us.yaml
+++ b/addons/core/translations/resources/en-us.yaml
@@ -577,6 +577,7 @@ target:
remove-address: Remove address and save
enable-session-recording: Enable Session Recording
enable-recording: Enable recording
+ view-more-sessions: View more sessions
types:
tcp: Generic TCP
ssh: SSH
diff --git a/ui/desktop/app/controllers/scopes/scope/projects/sessions/index.js b/ui/desktop/app/controllers/scopes/scope/projects/sessions/index.js
index b705f79cf..0ff59a558 100644
--- a/ui/desktop/app/controllers/scopes/scope/projects/sessions/index.js
+++ b/ui/desktop/app/controllers/scopes/scope/projects/sessions/index.js
@@ -4,6 +4,7 @@
*/
import Controller from '@ember/controller';
+import { inject as service } from '@ember/service';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
import orderBy from 'lodash/orderBy';
@@ -12,11 +13,23 @@ import { statusTypes } from 'api/models/session';
export default class ScopesScopeProjectsSessionsIndexController extends Controller {
// =services
+ @service intl;
+
// =attributes
- queryParams = [{ targets: { type: 'array' } }];
+ queryParams = [
+ { targets: { type: 'array' } },
+ { status: { type: 'array' } },
+ { scopes: { type: 'array' } },
+ 'page',
+ 'pageSize',
+ ];
@tracked targets = [];
+ @tracked status = [];
+ @tracked scopes = [];
+ @tracked page = 1;
+ @tracked pageSize = 10;
// =methods
@@ -45,17 +58,34 @@ export default class ScopesScopeProjectsSessionsIndexController extends Controll
* Returns all status types for sessions
* @returns {[string]}
*/
- get sessionStatusTypes() {
- return statusTypes;
+ get sessionStatusOptions() {
+ return statusTypes.map((status) => ({
+ id: status,
+ name: this.intl.t(`resources.session.status.${status}`),
+ }));
}
/**
- * Sets the targets query param to value of selectedTargets
+ * Returns scopes that are associated with all sessions the user has access to
+ * @returns {[ScopeModel]}
+ */
+ get availableScopes() {
+ const uniqueSessionScopeIds = new Set(
+ this.model.allSessions.map((session) => session.scope.id),
+ );
+ return this.model.projects.filter((project) =>
+ uniqueSessionScopeIds.has(project.id),
+ );
+ }
+
+ /**
+ * Sets the query params to value of selectedItems
* to trigger a query and closes the dropdown
* @param {object} selectedTargets
*/
@action
- applyFilter(filter, selectedTargets) {
- this[filter] = [...selectedTargets];
+ applyFilter(filter, selectedItems) {
+ this[filter] = [...selectedItems];
+ this.page = 1;
}
}
diff --git a/ui/desktop/app/controllers/scopes/scope/projects/targets/index.js b/ui/desktop/app/controllers/scopes/scope/projects/targets/index.js
index 63a7ba9aa..68eec0def 100644
--- a/ui/desktop/app/controllers/scopes/scope/projects/targets/index.js
+++ b/ui/desktop/app/controllers/scopes/scope/projects/targets/index.js
@@ -11,6 +11,10 @@ import { tracked } from '@glimmer/tracking';
import { debounce } from 'core/decorators/debounce';
import { notifySuccess, notifyError } from 'core/decorators/notify';
import orderBy from 'lodash/orderBy';
+import {
+ STATUS_SESSION_ACTIVE,
+ STATUS_SESSION_PENDING,
+} from 'api/models/session';
export default class ScopesScopeProjectsTargetsIndexController extends Controller {
// =services
@@ -93,14 +97,36 @@ export default class ScopesScopeProjectsTargetsIndexController extends Controlle
/**
* Returns active and pending sessions associated with a target
- * sorted descending by created time.
+ * sorted descending by created time with a limit of 10.
* @returns {object}
*/
get sortedTargetSessions() {
- const sessions = this.selectedTarget.sessions.filter(
- (session) => session.isAvailable,
- );
- return orderBy(sessions, 'created_time', 'desc');
+ return orderBy(
+ this.selectedTarget.availableSessions,
+ 'created_time',
+ 'desc',
+ ).slice(0, 10);
+ }
+
+ /**
+ * Returns true when there are more than 10 active or pending sessions
+ * associated with the selected target.
+ * @returns {boolean}
+ */
+ get showFlyoutViewMoreLink() {
+ return this.selectedTarget.availableSessions.length > 10;
+ }
+
+ /**
+ * Returns query params for filters that should be present when user
+ * clicks on the link to navigate to sessions route.
+ * @returns {object}
+ */
+ get viewMoreLinkQueryParams() {
+ return {
+ targets: [this.selectedTarget.id],
+ status: [STATUS_SESSION_ACTIVE, STATUS_SESSION_PENDING],
+ };
}
/**
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 d5f7aa92c..1886f0c3e 100644
--- a/ui/desktop/app/routes/scopes/scope/projects/sessions/index.js
+++ b/ui/desktop/app/routes/scopes/scope/projects/sessions/index.js
@@ -19,6 +19,20 @@ export default class ScopesScopeProjectsSessionsIndexRoute extends Route {
refreshModel: true,
replace: true,
},
+ status: {
+ refreshModel: true,
+ replace: true,
+ },
+ scopes: {
+ refreshModel: true,
+ replace: true,
+ },
+ page: {
+ refreshModel: true,
+ },
+ pageSize: {
+ refreshModel: true,
+ },
};
allSessions;
@@ -27,28 +41,37 @@ export default class ScopesScopeProjectsSessionsIndexRoute extends Route {
// =methods
/**
- * Loads all sessions under current scope for the current user.
- *
- * NOTE: previously, sessions were filtered only with API filter queries.
- * In an effort to offload processing from the controller, sessions
- * are now filtered on the client by projects and status,
- * while user_id filtering remains server side.
- *
- * @return {Promise<{sessions: [SessionModel], allSessions: [SessionModel]}>}
+ * Loads queried sessions, the total number of sessions, all sessions,
+ * all targets and all projects for filtering options for the current user.
+ * @return {Promise<{sessions: [SessionModel], projects: [ScopeModel], allSessions: [SessionModel], allTargets: [TargetModel], totalItems: number}>}
*/
- async model({ targets }, transition) {
+ async model({ targets, status, scopes, page, pageSize }, transition) {
const from = transition.from?.name;
const filters = {
user_id: [{ equals: this.session.data.authenticated.user_id }],
target_id: [],
+ status: [],
+ scope_id: [],
};
targets.forEach((target) => {
filters.target_id.push({ equals: target });
});
+ status.forEach((item) => {
+ filters.status.push({ equals: item });
+ });
+ scopes.forEach((scope) => {
+ filters.scope_id.push({ equals: scope });
+ });
- const queryOptions = { query: { filters }, force_refresh: true };
+ const queryOptions = {
+ query: { filters },
+ page,
+ pageSize,
+ force_refresh: true,
+ };
const sessions = await this.store.query('session', queryOptions);
+ const totalItems = sessions.meta?.totalItems;
// Query all sessions and all targets for defining filtering values if entering route for the first time
if (from !== 'scopes.scope.projects.sessions.index') {
@@ -67,10 +90,14 @@ export default class ScopesScopeProjectsSessionsIndexRoute extends Route {
this.allTargets = await this.store.query('target', {}, options);
}
+ const projects = this.modelFor('scopes.scope.projects');
+
return {
sessions,
+ projects,
allSessions: this.allSessions,
allTargets: this.allTargets,
+ totalItems,
};
}
}
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 e3f41c644..ae6c896a6 100644
--- a/ui/desktop/app/templates/scopes/scope/projects/sessions/index.hbs
+++ b/ui/desktop/app/templates/scopes/scope/projects/sessions/index.hbs
@@ -26,20 +26,42 @@
@listPosition='bottom-left'
/>
-
-
- {{#each this.sessionStatusTypes as |status|}}
- {{t
- (concat 'resources.session.status.' status)
- }}
- {{/each}}
-
-
-
+
-
+
+
+
+ {{#let (group-by itemOptions 'scopeModel') as |orgs|}}
+ {{#each orgs as |org|}}
+
+
+ {{org.key.displayName}}
+
+ {{#each org.items as |project|}}
+
+ {{project.displayName}}
+
+ {{/each}}
+ {{/each}}
+ {{/let}}
+
+
{{#if @model.sessions}}
+
{{else}}
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 a5a8ed35a..8ce17c50b 100644
--- a/ui/desktop/app/templates/scopes/scope/projects/targets/index.hbs
+++ b/ui/desktop/app/templates/scopes/scope/projects/targets/index.hbs
@@ -256,6 +256,15 @@
+ {{#if this.showFlyoutViewMoreLink}}
+
+ {{/if}}
{{/if}}