Skip to content

Commit 8c77c9d

Browse files
authored
Repository list - add columns, filters (#3787)
* Repository list sync status - merge status and date, show no remote vs never synced Combine sync status and last sync into a single column. This should still show the last sync status and date. The date could be a popover. If no remote is set on the repository, this column should say something like "no remote". If a remote is set, but a sync has never been performed this should say something like "never synced". Issue: AAH-2271 * Repository list - add Labels, Private columns Add a "Labels" column that includes the pipeline and hide from search label Add a "Private" column that indicates if the repo is private or not Issue: AAH-2271 * CHANGES * Add a "Pipeline" filter that lets the user select approved or staging repositories filter present repository list and approvals filter, but called Status, changed to Pipeline * ListPage: clean inputText on search Issue: AAH-2340 * page: remove unused didMount, extraState * Repository list - filter by Remote (or None) add typeahead filter by Remote, with an extra None (remote=null) option Issue: AAH-2271 * filter fixup - clean both inputText and selectedFilter
1 parent e9cab1b commit 8c77c9d

File tree

10 files changed

+176
-145
lines changed

10 files changed

+176
-145
lines changed

CHANGES/2271.feature

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Repository list - add columns, filters

CHANGES/2340.bugfix

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Repositories, Remotes - clean filter text on search

src/api/response-types/ansible-repository.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
1+
import { LastSyncType } from './remote';
2+
13
export class AnsibleRepositoryType {
24
description: string;
5+
last_sync_task?: LastSyncType;
36
latest_version_href?: string;
47
name: string;
8+
private?: boolean;
59
pulp_created?: string;
610
pulp_href?: string;
711
pulp_labels?: { [key: string]: string };
812
remote?: string;
913
retain_repo_versions: number;
10-
private?: boolean;
1114

1215
// gpgkey
1316
// last_synced_metadata_time

src/components/page/list-page.tsx

+49-36
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
EmptyStateFilter,
1717
EmptyStateNoData,
1818
EmptyStateUnauthorized,
19+
FilterOption,
1920
LoadingPageSpinner,
2021
Main,
2122
Pagination,
@@ -43,6 +44,7 @@ interface IState<T> {
4344
alerts: AlertType[];
4445
unauthorised: boolean;
4546
inputText: string;
47+
selectedFilter: string;
4648
}
4749

4850
// states:
@@ -76,16 +78,13 @@ export type LocalizedSortHeaders = {
7678
className?: string;
7779
}[];
7880

79-
interface ListPageParams<T, ExtraState> {
81+
interface ListPageParams<T> {
8082
condition: PermissionContextType;
8183
defaultPageSize: number;
8284
defaultSort?: string;
83-
didMount?: ({ context, addAlert }) => void;
8485
displayName: string;
8586
errorTitle: MessageDescriptor;
86-
extraState?: ExtraState;
87-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
88-
filterConfig: any[]; // FilterOption[] but { title: MessageDescriptor }
87+
filterConfig: ({ state }) => FilterOption[];
8988
headerActions?: ActionType[];
9089
listItemActions?: ActionType[];
9190
noDataButton?: (item, actionContext) => React.ReactNode;
@@ -96,13 +95,12 @@ interface ListPageParams<T, ExtraState> {
9695
renderTableRow: RenderTableRow<T>;
9796
sortHeaders: SortHeaders;
9897
title: MessageDescriptor;
98+
typeaheadQuery?: ({ inputText, selectedFilter, setState }) => void;
9999
}
100100

101-
export const ListPage = function <T, ExtraState = Record<string, never>>({
101+
export const ListPage = function <T>({
102102
// { featureFlags, settings, user } => bool
103103
condition,
104-
// extra code to run on mount
105-
didMount,
106104
// component name for debugging
107105
displayName,
108106
// initial page size
@@ -111,8 +109,6 @@ export const ListPage = function <T, ExtraState = Record<string, never>>({
111109
defaultSort,
112110
// alert on query failure
113111
errorTitle,
114-
// extra initial state
115-
extraState,
116112
// filters
117113
filterConfig,
118114
// displayed after filters
@@ -133,7 +129,9 @@ export const ListPage = function <T, ExtraState = Record<string, never>>({
133129
sortHeaders,
134130
// container title
135131
title,
136-
}: ListPageParams<T, ExtraState>) {
132+
// for typeahed filters
133+
typeaheadQuery,
134+
}: ListPageParams<T>) {
137135
renderModals ||= function (actionContext) {
138136
return (
139137
<>
@@ -180,8 +178,8 @@ export const ListPage = function <T, ExtraState = Record<string, never>>({
180178
items: [],
181179
loading: true,
182180
params,
181+
selectedFilter: null,
183182
unauthorised: false,
184-
...extraState,
185183
};
186184
}
187185

@@ -194,26 +192,13 @@ export const ListPage = function <T, ExtraState = Record<string, never>>({
194192

195193
this.setState({ alerts: this.context.alerts || [] });
196194
this.context.setAlerts([]);
197-
198-
if (didMount) {
199-
didMount({
200-
context: this.context,
201-
addAlert: (alert) => this.addAlert(alert),
202-
});
203-
}
204195
}
205196

206197
render() {
207198
const { alerts, itemCount, items, loading, params, unauthorised } =
208199
this.state;
209200

210-
const localizedFilterConfig = (filterConfig || [])
211-
.map(translateTitle)
212-
.map(({ options, ...rest }) => ({
213-
...rest,
214-
options: options?.map(translateTitle),
215-
}));
216-
201+
const localizedFilterConfig = filterConfig({ state: this.state }) || [];
217202
const knownFilters = localizedFilterConfig.map(({ id }) => id);
218203
const noData = items.length === 0 && !filterIsSet(params, knownFilters);
219204

@@ -225,11 +210,11 @@ export const ListPage = function <T, ExtraState = Record<string, never>>({
225210

226211
const niceValues = {};
227212
localizedFilterConfig
228-
.filter((filter) => filter['options'] && filter['options'].length > 0)
229-
.forEach((item) => {
230-
const obj = (niceValues[item['id']] = {});
231-
item['options'].forEach((option) => {
232-
obj[option.id] = option.title;
213+
.filter(({ options }) => options?.length)
214+
.forEach(({ id: filterId, options }) => {
215+
const obj = (niceValues[filterId] = {});
216+
options.forEach(({ id: optionId, title }) => {
217+
obj[optionId] = title;
233218
});
234219
});
235220

@@ -245,6 +230,12 @@ export const ListPage = function <T, ExtraState = Record<string, never>>({
245230
user: this.context.user,
246231
};
247232

233+
const resetCompoundFilter = () =>
234+
this.setState({
235+
inputText: '',
236+
selectedFilter: localizedFilterConfig[0].id,
237+
});
238+
248239
return (
249240
<React.Fragment>
250241
<AlertList alerts={alerts} closeAlert={(i) => this.closeAlert(i)} />
@@ -271,12 +262,34 @@ export const ListPage = function <T, ExtraState = Record<string, never>>({
271262
<ToolbarItem>
272263
<CompoundFilter
273264
inputText={this.state.inputText}
274-
onChange={(inputText) =>
275-
this.setState({ inputText })
276-
}
277-
updateParams={updateParams}
265+
onChange={(inputText) => {
266+
this.setState({ inputText });
267+
268+
if (typeaheadQuery) {
269+
typeaheadQuery({
270+
inputText,
271+
selectedFilter: this.state.selectedFilter,
272+
setState: (s) => this.setState(s),
273+
});
274+
}
275+
}}
276+
updateParams={(p) => {
277+
resetCompoundFilter();
278+
updateParams(p);
279+
}}
278280
params={params}
279281
filterConfig={localizedFilterConfig}
282+
selectFilter={(selectedFilter) => {
283+
this.setState({ selectedFilter });
284+
285+
if (typeaheadQuery) {
286+
typeaheadQuery({
287+
inputText: '',
288+
selectedFilter,
289+
setState: (s) => this.setState(s),
290+
});
291+
}
292+
}}
280293
/>
281294
</ToolbarItem>
282295
{headerActions?.length &&
@@ -299,8 +312,8 @@ export const ListPage = function <T, ExtraState = Record<string, never>>({
299312
<div>
300313
<AppliedFilters
301314
updateParams={(p) => {
315+
resetCompoundFilter();
302316
updateParams(p);
303-
this.setState({ inputText: '' });
304317
}}
305318
params={params}
306319
ignoredParams={['page_size', 'page', 'sort', 'ordering']}

src/components/page/page-with-tabs.tsx

+2-17
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,11 @@ interface IState<T> {
4646

4747
type RenderModals = ({ addAlert, state, setState, query }) => React.ReactNode;
4848

49-
interface PageWithTabsParams<T, ExtraState> {
49+
interface PageWithTabsParams<T> {
5050
breadcrumbs: ({ name, tab, params }) => { url?: string; name: string }[];
5151
condition: PermissionContextType;
52-
didMount?: ({ context, addAlert }) => void;
5352
displayName: string;
5453
errorTitle: MessageDescriptor;
55-
extraState?: ExtraState;
5654
headerActions?: ActionType[];
5755
headerDetails?: (item) => React.ReactNode;
5856
query: ({ name }) => Promise<T>;
@@ -64,20 +62,15 @@ interface PageWithTabsParams<T, ExtraState> {
6462

6563
export const PageWithTabs = function <
6664
T extends { name: string; my_permissions?: string[] },
67-
ExtraState = Record<string, never>,
6865
>({
6966
// ({ name }) => [{ url?, name }]
7067
breadcrumbs,
7168
// { featureFlags, settings, user } => bool
7269
condition,
73-
// extra code to run on mount
74-
didMount,
7570
// component name for debugging
7671
displayName,
7772
// alert on query failure
7873
errorTitle,
79-
// extra initial state
80-
extraState,
8174
// displayed after filters
8275
headerActions,
8376
// under title
@@ -91,7 +84,7 @@ export const PageWithTabs = function <
9184
tabs,
9285
// params => params
9386
tabUpdateParams,
94-
}: PageWithTabsParams<T, ExtraState>) {
87+
}: PageWithTabsParams<T>) {
9588
renderModals ||= function (actionContext) {
9689
return (
9790
<>
@@ -121,7 +114,6 @@ export const PageWithTabs = function <
121114
loading: true,
122115
unauthorised: false,
123116
params,
124-
...extraState,
125117
};
126118
}
127119

@@ -134,13 +126,6 @@ export const PageWithTabs = function <
134126

135127
this.setState({ alerts: this.context.alerts || [] });
136128
this.context.setAlerts([]);
137-
138-
if (didMount) {
139-
didMount({
140-
context: this.context,
141-
addAlert: (alert) => this.addAlert(alert),
142-
});
143-
}
144129
}
145130

146131
componentDidUpdate(prevProps) {

src/components/page/page.tsx

+2-17
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,11 @@ interface IState<T> {
3535

3636
type RenderModals = ({ addAlert, state, setState, query }) => React.ReactNode;
3737

38-
interface PageParams<T, ExtraState> {
38+
interface PageParams<T> {
3939
breadcrumbs: ({ name }) => { url?: string; name: string }[];
4040
condition: PermissionContextType;
41-
didMount?: ({ context, addAlert }) => void;
4241
displayName: string;
4342
errorTitle: MessageDescriptor;
44-
extraState?: ExtraState;
4543
headerActions?: ActionType[];
4644
query: ({ name }) => Promise<T>;
4745
title: ({ name }) => string;
@@ -52,20 +50,15 @@ interface PageParams<T, ExtraState> {
5250

5351
export const Page = function <
5452
T extends { name: string; my_permissions?: string[] },
55-
ExtraState = Record<string, never>,
5653
>({
5754
// ({ name }) => [{ url?, name }]
5855
breadcrumbs,
5956
// { featureFlags, settings, user } => bool
6057
condition,
61-
// extra code to run on mount
62-
didMount,
6358
// component name for debugging
6459
displayName,
6560
// alert on query failure
6661
errorTitle,
67-
// extra initial state
68-
extraState,
6962
// displayed after filters
7063
headerActions,
7164
// () => Promise<T>
@@ -75,7 +68,7 @@ export const Page = function <
7568
// ({ addAlert, state, setState, query }) => <ConfirmationModal... />
7669
renderModals,
7770
render,
78-
}: PageParams<T, ExtraState>) {
71+
}: PageParams<T>) {
7972
renderModals ||= function (actionContext) {
8073
return (
8174
<>
@@ -98,7 +91,6 @@ export const Page = function <
9891
item: null,
9992
loading: true,
10093
unauthorised: false,
101-
...extraState,
10294
};
10395
}
10496

@@ -116,13 +108,6 @@ export const Page = function <
116108

117109
this.setState({ alerts: this.context.alerts || [] });
118110
this.context.setAlerts([]);
119-
120-
if (didMount) {
121-
didMount({
122-
context: this.context,
123-
addAlert: (alert) => this.addAlert(alert),
124-
});
125-
}
126111
});
127112
}
128113

src/components/patternfly-wrappers/compound-filter.tsx

+5-4
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@ export class CompoundFilter extends React.Component<IProps, IState> {
5454
constructor(props) {
5555
super(props);
5656

57-
// this is called again in repositories selector, but not in approval page (the same filter is here)...
5857
this.state = {
5958
selectedFilter: props.filterConfig[0],
6059
isExpanded: false,
@@ -167,9 +166,10 @@ export class CompoundFilter extends React.Component<IProps, IState> {
167166
/>
168167
);
169168
case 'typeahead': {
170-
const typeAheadResults = this.props.filterConfig
169+
const typeaheadResults = this.props.filterConfig
171170
.find(({ id }) => id === selectedFilter.id)
172171
.options.map(({ id, title }) => ({ id, name: title }));
172+
173173
return (
174174
<APISearchTypeAhead
175175
multiple={false}
@@ -180,13 +180,14 @@ export class CompoundFilter extends React.Component<IProps, IState> {
180180
this.props.onChange('');
181181
}}
182182
onSelect={(event, value) => {
183-
this.submitFilter(value);
183+
const item = typeaheadResults.find(({ name }) => name === value);
184+
this.submitFilter(item?.id || value);
184185
}}
185186
placeholderText={
186187
selectedFilter?.placeholder ||
187188
t`Filter by ${selectedFilter.title.toLowerCase()}`
188189
}
189-
results={typeAheadResults}
190+
results={typeaheadResults}
190191
/>
191192
);
192193
}

0 commit comments

Comments
 (0)