Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ui): Add option to sort applications list by name and deploy time #6217

Closed
wants to merge 10 commits into from
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,28 @@
}
}

&__sort {
display: flex;
align-items: center;
margin-bottom: 0.5em;

&__options {
display: flex;
align-items: center;
margin-left: auto;
}

&__option {
cursor: pointer;
line-height: 2em;
margin-right: 13px;

i {
margin-right: 3px;
}
}
}

&__accordion {
cursor: pointer;
text-align: center;
Expand Down Expand Up @@ -195,6 +217,6 @@
justify-content: space-evenly;
}
}
i.menu_icon{
i.menu_icon {
vertical-align: middle;
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {bufferTime, delay, filter, map, mergeMap, repeat, retryWhen} from 'rxjs/
import {AddAuthToToolbar, ClusterCtx, DataLoader, EmptyState, ObservableQuery, Page, Paginate, Query, Spinner} from '../../../shared/components';
import {Consumer, Context, ContextApis} from '../../../shared/context';
import * as models from '../../../shared/models';
import {AppsListViewKey, AppsListPreferences, AppsListViewType, HealthStatusBarPreferences, services} from '../../../shared/services';
import {AppsListViewKey, AppsListPreferences, AppsListViewType, HealthStatusBarPreferences, services, SortAppsBy} from '../../../shared/services';
import {ApplicationCreatePanel} from '../application-create-panel/application-create-panel';
import {ApplicationSyncPanel} from '../application-sync-panel/application-sync-panel';
import {ApplicationsSyncPanel} from '../applications-sync-panel/applications-sync-panel';
Expand Down Expand Up @@ -283,6 +283,45 @@ const FlexTopBar = (props: {toolbar: Toolbar | Observable<Toolbar>}) => {
);
};

type AppSorter = (a: models.Application, b: models.Application) => number;

const AppSorters: {[key: string]: AppSorter} = {
'Name': ((a, b) => {
if (a && a.metadata && b && b.metadata) {
return a.metadata.name < b.metadata.name ? -1 : 1;
}
return 1;
}) as AppSorter,
'Deployment Time': ((a, b) => (a.status.observedAt < b.status.observedAt ? 1 : -1)) as AppSorter
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like the ApplicationStatus.ObservedAt field has been deprecated, probably ApplicationStatus.OperationState.FinishedAt would be a better replacement?

};

const ApplicationSortOptions = (props: {onChange: (sorter: SortAppsBy) => void; init: SortAppsBy}) => {
const [sorter, setSorter] = React.useState<SortAppsBy>(props.init || SortAppsBy.Name);
React.useEffect(() => {
props.onChange(sorter);
}, [sorter]);

return (
<div className='applications-list__sort'>
<div className='applications-list__filters__title' style={{marginRight: '15px'}}>
SORT BY <i className='fa fa-sort' />
</div>
<div className='applications-list__sort__options'>
{Object.values(SortAppsBy).map(opt => (
<div
className='applications-list__sort__option'
key={opt}
onClick={() => {
setSorter(sorter === opt ? null : opt);
}}>
<i className={`fa fa-${sorter === opt ? 'dot-circle' : 'circle'}`} /> {opt}
</div>
))}
</div>
</div>
);
};

export const ApplicationsList = (props: RouteComponentProps<{}>) => {
const query = new URLSearchParams(props.location.search);
const appInput = tryJsonParse(query.get('new'));
Expand Down Expand Up @@ -360,7 +399,12 @@ export const ApplicationsList = (props: RouteComponentProps<{}>) => {
)}>
{(applications: models.Application[]) => {
const healthBarPrefs = pref.statusBarView || ({} as HealthStatusBarPreferences);
const {filteredApps, filterResults} = filterApps(applications, pref, pref.search);
const res = filterApps(applications, pref, pref.search);
const {filterResults} = res;
let {filteredApps} = res;
if (pref.sorter) {
filteredApps = filteredApps.sort(AppSorters[pref.sorter]);
}
return (
<React.Fragment>
<FlexTopBar
Expand Down Expand Up @@ -451,50 +495,61 @@ export const ApplicationsList = (props: RouteComponentProps<{}>) => {
</button>
</EmptyState>
) : (
<ApplicationsFilter apps={filterResults} onChange={newPrefs => onFilterPrefChanged(ctx, newPrefs)} pref={pref}>
{(pref.view === 'summary' && <ApplicationsSummary applications={filteredApps} />) || (
<Paginate
header={filteredApps.length > 1 && <ApplicationsStatusBar applications={filteredApps} />}
showHeader={healthBarPrefs.showHealthStatusBar}
preferencesKey='applications-list'
page={pref.page}
emptyState={() => (
<EmptyState icon='fa fa-search'>
<h4>No matching applications found</h4>
<h5>
Change filter criteria or&nbsp;
<a
onClick={() => {
AppsListPreferences.clearFilters(pref);
onFilterPrefChanged(ctx, pref);
}}>
clear filters
</a>
</h5>
</EmptyState>
)}
data={filteredApps}
onPageChange={page => ctx.navigation.goto('.', {page})}>
{data =>
(pref.view === 'tiles' && (
<ApplicationTiles
applications={data}
syncApplication={appName => ctx.navigation.goto('.', {syncApp: appName}, {replace: true})}
refreshApplication={refreshApp}
deleteApplication={appName => AppUtils.deleteApplication(appName, ctx)}
/>
)) || (
<ApplicationsTable
applications={data}
syncApplication={appName => ctx.navigation.goto('.', {syncApp: appName}, {replace: true})}
refreshApplication={refreshApp}
deleteApplication={appName => AppUtils.deleteApplication(appName, ctx)}
<React.Fragment>
<ApplicationsFilter apps={filterResults} onChange={newPrefs => onFilterPrefChanged(ctx, newPrefs)} pref={pref}>
{(pref.view === 'summary' && <ApplicationsSummary applications={filteredApps} />) || (
<Paginate
header={filteredApps.length > 1 && <ApplicationsStatusBar applications={filteredApps} />}
tools={
<ApplicationSortOptions
init={pref.sorter}
onChange={sorter => {
services.viewPreferences.updatePreferences({appList: {...pref, sorter}});
ctx.navigation.goto('.', {sorter});
}}
/>
)
}
</Paginate>
)}
</ApplicationsFilter>
}
showHeader={healthBarPrefs.showHealthStatusBar}
preferencesKey='applications-list'
page={pref.page}
emptyState={() => (
<EmptyState icon='fa fa-search'>
<h4>No matching applications found</h4>
<h5>
Change filter criteria or&nbsp;
<a
onClick={() => {
AppsListPreferences.clearFilters(pref);
onFilterPrefChanged(ctx, pref);
}}>
clear filters
</a>
</h5>
</EmptyState>
)}
data={filteredApps}
onPageChange={page => ctx.navigation.goto('.', {page})}>
{data =>
(pref.view === 'tiles' && (
<ApplicationTiles
applications={data}
syncApplication={appName => ctx.navigation.goto('.', {syncApp: appName}, {replace: true})}
refreshApplication={refreshApp}
deleteApplication={appName => AppUtils.deleteApplication(appName, ctx)}
/>
)) || (
<ApplicationsTable
applications={data}
syncApplication={appName => ctx.navigation.goto('.', {syncApp: appName}, {replace: true})}
refreshApplication={refreshApp}
deleteApplication={appName => AppUtils.deleteApplication(appName, ctx)}
/>
)
}
</Paginate>
)}
</ApplicationsFilter>
</React.Fragment>
)}
<ApplicationsSyncPanel
key='syncsPanel'
Expand Down
4 changes: 3 additions & 1 deletion ui/src/app/shared/components/paginate/paginate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@ export interface PaginateProps<T> {
preferencesKey?: string;
header?: React.ReactNode;
showHeader?: boolean;
tools?: React.ReactNode;
}

export function Paginate<T>({page, onPageChange, children, data, emptyState, preferencesKey, header, showHeader}: PaginateProps<T>) {
export function Paginate<T>({page, onPageChange, children, data, emptyState, preferencesKey, header, showHeader, tools}: PaginateProps<T>) {
return (
<DataLoader load={() => services.viewPreferences.getPreferences()}>
{pref => {
Expand All @@ -42,6 +43,7 @@ export function Paginate<T>({page, onPageChange, children, data, emptyState, pre
onPageChange={item => onPageChange(item.selected)}
/>
)}
{tools}
<div className='paginate__size-menu'>
<DropDownMenu
anchor={() => (
Expand Down
8 changes: 8 additions & 0 deletions ui/src/app/shared/services/view-preferences-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ export enum AppsDetailsViewKey {
Pods = 'pods'
}

export enum SortAppsBy {
Name = 'Name',
DeploymentTime = 'Deployment Time'
}

export interface AppDetailsPreferences {
resourceFilter: string[];
view: AppsDetailsViewType;
Expand Down Expand Up @@ -63,6 +68,7 @@ export class AppsListPreferences {
pref.projectsFilter = [];
pref.reposFilter = [];
pref.syncFilter = [];
pref.sorter = null;
pref.showFavorites = false;
}

Expand All @@ -74,6 +80,7 @@ export class AppsListPreferences {
public namespacesFilter: string[];
public clustersFilter: string[];
public view: AppsListViewType;
public sorter: SortAppsBy | null;
public hideFilters: boolean;
public statusBarView: HealthStatusBarPreferences;
public showFavorites: boolean;
Expand Down Expand Up @@ -119,6 +126,7 @@ const DEFAULT_PREFERENCES: ViewPreferences = {
reposFilter: new Array<string>(),
syncFilter: new Array<string>(),
healthFilter: new Array<string>(),
sorter: SortAppsBy.Name,
hideFilters: false,
showFavorites: false,
favoritesAppList: new Array<string>(),
Expand Down