fix: 🐛 Add refresh button back (#2124)

This commit is contained in:
Zhihe Li authored and GitHub committed 2024-01-30 19:54:34 -05:00
1 parent 4d3c993b0f
commit 06cc64e6af
7 files changed
+233 -164

No files matched your search

+20
View File
@@ -38,6 +38,26 @@
.hds-table {
margin-bottom: 1rem;
}
.search-filtering-toolbar {
display: flex;
// TODO: Remove these copied styles by migrating to HDS for the toolbar refresher component.
// The original styles assumed we were using the old toolbar component under a "rose-toolbar" class.
.rose-button-inline-link-action {
font-size: 0.8125rem;
line-height: 1.8461538462;
font-weight: 600;
color: var(--stark);
display: inline-block;
vertical-align: middle;
padding: 0.375rem 1rem;
}
> :last-child {
margin-left: auto;
}
}
}
// Flyout with table
@@ -7,19 +7,17 @@
@use '../../variables/media';
@use '../../utilities/type';
.rose-toolbar {
.rose-toolbar-info {
@include type.type(button);
.rose-toolbar-info {
@include type.type(button);
color: var(--ui-gray);
position: relative;
display: inline-block;
vertical-align: middle;
white-space: nowrap;
overflow: hidden;
color: var(--ui-gray);
position: relative;
display: inline-block;
vertical-align: middle;
white-space: nowrap;
overflow: hidden;
$y-padding: sizing.rems(xxs) + sizing.rems(xxxs);
$y-padding: sizing.rems(xxs) + sizing.rems(xxxs);
padding: $y-padding sizing.rems(m) $y-padding sizing.rems(l);
}
padding: $y-padding sizing.rems(m) $y-padding sizing.rems(l);
}
@@ -18,6 +18,7 @@ export default class ScopesScopeProjectsSessionsIndexRoute extends Route {
@service session;
@service store;
@service ipc;
@service router;
// =attributes
@@ -66,8 +67,7 @@ export default class ScopesScopeProjectsSessionsIndexRoute extends Route {
* 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, status, scopes, page, pageSize }, transition) {
const from = transition.from?.name;
async model({ targets, status, scopes, page, pageSize }) {
const projects = this.modelFor('scopes.scope.projects');
const orgScope = this.modelFor('scopes.scope');
// orgFilter used to narrow down resources to only those under
@@ -105,34 +105,11 @@ export default class ScopesScopeProjectsSessionsIndexRoute extends Route {
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') {
const options = { pushToStore: false };
const allSessionsQuery = {
scope_id: orgScope.id,
recursive: true,
query: {
filters: {
user_id: [{ equals: this.session.data.authenticated.user_id }],
},
},
};
if (orgScope.isOrg && scopes.length === 0) {
allSessionsQuery.filter = orgFilter;
}
this.allSessions = await this.store.query(
'session',
allSessionsQuery,
options,
);
const allTargetsQuery = { scope_id: orgScope.id, recursive: true };
if (orgScope.isOrg) {
allTargetsQuery.filter = orgFilter;
}
this.allTargets = await this.store.query(
'target',
allTargetsQuery,
options,
);
if (!this.allSessions) {
await this.getAllSessions(orgScope, scopes, orgFilter);
}
if (!this.allTargets) {
await this.getAllTargets(orgScope, orgFilter);
}
return {
@@ -145,6 +122,39 @@ export default class ScopesScopeProjectsSessionsIndexRoute extends Route {
};
}
async getAllTargets(orgScope, orgFilter) {
const allTargetsQuery = {
scope_id: orgScope.id,
recursive: true,
force_refresh: true,
};
if (orgScope.isOrg) {
allTargetsQuery.filter = orgFilter;
}
this.allTargets = await this.store.query('target', allTargetsQuery, {
pushToStore: false,
});
}
async getAllSessions(orgScope, scopes, orgFilter) {
const allSessionsQuery = {
scope_id: orgScope.id,
recursive: true,
query: {
filters: {
user_id: [{ equals: this.session.data.authenticated.user_id }],
},
},
force_refresh: true,
};
if (orgScope.isOrg && scopes.length === 0) {
allSessionsQuery.filter = orgFilter;
}
this.allSessions = await this.store.query('session', allSessionsQuery, {
pushToStore: false,
});
}
resetController(controller, isExiting, transition) {
const fromScope = transition.from.find(
(routeInfo) => routeInfo.name === 'scopes.scope',
@@ -166,4 +176,20 @@ export default class ScopesScopeProjectsSessionsIndexRoute extends Route {
});
}
}
@action
async refreshAll() {
const orgScope = this.modelFor('scopes.scope');
const orgFilter = `"/item/scope/parent_scope_id" == "${orgScope.id}"`;
await this.getAllTargets(orgScope, orgFilter);
await this.getAllSessions(orgScope, [], orgFilter);
// Prime the store by searching for orgs only in case there are new org scopes;
// otherwise we won't be able to correctly peek the org scopes for their display name in the UI.
await this.store.query('scope', {});
// Refresh the proj scopes so our `modelFor` returns accurate data
this.router.refresh('scopes.scope.projects');
}
}
@@ -80,15 +80,16 @@ export default class ScopesScopeProjectsTargetsIndexRoute extends Route {
*
* @returns {Promise<{totalItems: number, targets: [TargetModel], projects: [ScopeModel], allTargets: [TargetModel] }> }
*/
async model(
{ search, scopes, availableSessions, types, page, pageSize },
transition,
) {
async model({ search, scopes, availableSessions, types, page, pageSize }) {
const orgScope = this.modelFor('scopes.scope');
// orgFilter used to narrow down resources to only those under
// the current org scope if org is not global
const orgFilter = `"/item/scope/parent_scope_id" == "${orgScope.id}"`;
await this.getAllTargets(transition, orgScope, orgFilter);
if (!this.allTargets) {
await this.getAllTargets(orgScope, orgFilter);
}
const projects = this.modelFor('scopes.scope.projects');
const filters = { scope_id: [], id: { values: [] }, type: [] };
@@ -172,26 +173,27 @@ export default class ScopesScopeProjectsTargetsIndexRoute extends Route {
/**
* 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.
* @param transition
* @param orgScope
* @param orgFilter
* @returns {Promise<void>}
*/
async getAllTargets(transition, orgScope, orgFilter) {
const from = transition.from?.name;
async getAllTargets(orgScope, orgFilter) {
// Query all targets for defining filtering values if entering route for first time
if (from !== 'scopes.scope.projects.targets.index') {
const query = { scope_id: orgScope.id, recursive: true };
if (orgScope.isOrg) {
query.filter = orgFilter;
}
const options = { pushToStore: false };
const allTargets = await this.store.query('target', query, options);
// Filter out targets to which users do not have the connect ability
this.allTargets = allTargets.filter((target) =>
target.authorized_actions.includes('authorize-session'),
);
const query = {
scope_id: orgScope.id,
recursive: true,
force_refresh: true,
};
if (orgScope.isOrg) {
query.filter = orgFilter;
}
const options = { pushToStore: false };
const allTargets = await this.store.query('target', query, options);
// Filter out targets to which users do not have the connect ability
this.allTargets = allTargets.filter((target) =>
target.authorized_actions.includes('authorize-session'),
);
}
/**
@@ -233,4 +235,18 @@ export default class ScopesScopeProjectsTargetsIndexRoute extends Route {
}
});
};
@action
async refreshAll() {
const orgScope = this.modelFor('scopes.scope');
const orgFilter = `"/item/scope/parent_scope_id" == "${orgScope.id}"`;
await this.getAllTargets(orgScope, orgFilter);
// Prime the store by searching for only orgs in case there are new org scopes;
// otherwise we won't be able to correctly peek the org scopes for their display name in the UI.
await this.store.query('scope', {});
// Refresh the proj scopes so our `modelFor` returns accurate data
this.router.refresh('scopes.scope.projects');
}
}
@@ -5,54 +5,60 @@
{{#if (or @model.sessions @model.allSessions)}}
{{#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>
<div class='search-filtering-toolbar'>
<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>
{{/let}}
</Dropdown>
</S.Generic>
</Hds::SegmentedGroup>
<span>
<ToolbarRefresher @onClick={{route-action 'refreshAll'}} />
</span>
</div>
<FilterTags @filters={{this.filters}} />
{{/if}}
{{#if @model.sessions}}
@@ -5,60 +5,65 @@
{{#if this.showFilters}}
{{#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>
<Dropdown
@name={{t 'resources.target.titles.active-sessions'}}
@itemOptions={{this.availableSessionOptions}}
@checkedItems={{this.availableSessions}}
@applyFilter={{fn this.applyFilter 'availableSessions'}}
<div class='search-filtering-toolbar'>
<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>
{{/let}}
</Dropdown>
</S.Generic>
</Hds::SegmentedGroup>
<span>
<ToolbarRefresher @onClick={{route-action 'refreshAll'}} />
</span>
</div>
<FilterTags @filters={{this.filters}} />
{{/if}}
@@ -373,8 +373,6 @@ module('Acceptance | projects | sessions | index', function (hooks) {
resource: 'sessions',
func: () => [instances.session2],
},
'sessions',
'targets',
);
await visit(urls.globalSessions);