Skip to content

Commit a034768

Browse files
authored
Merge branch 'main' into bs-table
2 parents 730f1e0 + 0cce0d8 commit a034768

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+933
-490
lines changed

CODEOWNERS

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,8 @@
1-
* @konveyor/migration-engineering-ui-committers
2-
/pkg/qe-tests/ @konveyor/migration-engineering-qe-committers
1+
#
2+
# Until an organization group (for example `@konveyor/tackle/ui-reviewers`) is created
3+
# and maintained to hold the set of people to auto-assign PRs for review, individually
4+
# name each person.
5+
#
6+
# The list (or group membership) should match up with the `OWNERS.md` file.
7+
#
8+
* @ibolton336 @sjd78 @rszwajko

Dockerfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ COPY --chown=1001 . .
1515
RUN npm clean-install --ignore-scripts && npm run build && npm run dist
1616

1717
# Runner image
18-
FROM registry.access.redhat.com/ubi9/nodejs-18-minimal:1-113.1716472876
18+
FROM registry.access.redhat.com/ubi9/nodejs-18-minimal:1-117
1919

2020
# Add ps package to allow liveness probe for k8s cluster
2121
# Add tar package to allow copying files with kubectl scp

client/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"build": "NODE_ENV=production webpack --config ./config/webpack.prod.ts",
1212
"build:dev": "NODE_ENV=development webpack --config ./config/webpack.dev.ts",
1313
"start:dev": "NODE_ENV=development webpack serve --config ./config/webpack.dev.ts",
14-
"test": "NODE_ENV=test jest --rootDir=. --config=./config/jest.config.ts",
14+
"test": "NODE_ENV=test TZ=UTC jest --rootDir=. --config=./config/jest.config.ts",
1515
"lint": "eslint .",
1616
"tsc": "tsc -p ./tsconfig.json"
1717
},

client/public/locales/en/translation.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
"delete": "Delete",
3232
"discardAssessment": "Discard assessment(s)",
3333
"discardReview": "Discard review",
34-
3534
"downloadCsvTemplate": "Download CSV template",
3635
"download": "Download {{what}}",
3736
"duplicate": "Duplicate",
@@ -105,7 +104,6 @@
105104
"dialog": {
106105
"message": {
107106
"applicationsBulkDelete": "The selected application(s) will be deleted.",
108-
109107
"delete": "This action cannot be undone.",
110108
"discardAssessment": "The assessment(s) for <1>{{applicationName}}</1> will be discarded. Do you wish to continue?",
111109
"discardReview": "The review for <1>{{applicationName}}</1> will be discarded. Do you wish to continue?",
@@ -332,6 +330,7 @@
332330
"effort": "Effort",
333331
"effortEstimate": "Effort estimate",
334332
"email": "Email",
333+
"endDate": "End date",
335334
"error": "Error",
336335
"errorReport": "Error report",
337336
"explanation": "Explanation",
@@ -434,6 +433,7 @@
434433
"stakeholderGroupDeleted": "Stakeholder group deleted",
435434
"stakeholderGroups": "Stakeholder groups",
436435
"stakeholders": "Stakeholders",
436+
"startDate": "Start date",
437437
"status": "Status",
438438
"suggestedAdoptionPlan": "Suggested adoption plan",
439439
"svnConfig": "Subversion configuration",

client/src/app/Paths.ts

+7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
export const DevPaths = {
22
// Developer perspective
33
applications: "/applications",
4+
applicationsAnalysisDetails:
5+
"/applications/:applicationId/analysis-details/:taskId",
46
applicationsAnalysisTab: "/applications/analysis-tab",
57
applicationsAssessmentTab: "/applications/assessment-tab",
68
applicationsImports: "/applications/application-imports",
@@ -86,3 +88,8 @@ export interface ReviewRoute {
8688
export interface ImportSummaryRoute {
8789
importId: string;
8890
}
91+
92+
export interface AnalysisDetailsRoute {
93+
applicationId: string;
94+
taskId: string;
95+
}

client/src/app/Routes.tsx

+8-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ const ManageImports = lazy(() => import("./pages/applications/manage-imports"));
2424
const ImportDetails = lazy(
2525
() => import("./pages/applications/manage-imports-details")
2626
);
27-
27+
const AnalysisDetails = lazy(
28+
() => import("./pages/applications/analysis-details")
29+
);
2830
const Reports = lazy(() => import("./pages/reports"));
2931
const Controls = lazy(() => import("./pages/controls"));
3032
const Identities = lazy(() => import("./pages/identities"));
@@ -74,6 +76,11 @@ export const devRoutes: IRoute<DevPathValues>[] = [
7476
comp: ImportDetails,
7577
exact: false,
7678
},
79+
{
80+
path: Paths.applicationsAnalysisDetails,
81+
comp: AnalysisDetails,
82+
exact: false,
83+
},
7784
{
7885
path: Paths.applicationsImports,
7986
comp: ManageImports,

client/src/app/api/models.ts

+50-19
Original file line numberDiff line numberDiff line change
@@ -307,18 +307,62 @@ export type TaskState =
307307

308308
export interface Task {
309309
id?: number;
310+
createUser?: string;
311+
updateUser?: string;
310312
createTime?: string;
311-
application: { id: number };
313+
312314
name: string;
315+
kind: string;
313316
addon: string;
317+
extensions: string[];
318+
state?: TaskState;
319+
locator?: string;
320+
priority?: number;
321+
policy: TaskPolicy;
322+
ttl: TTL;
314323
data: TaskData;
315-
error?: string;
316-
image?: string;
324+
application: Ref;
325+
bucket?: Ref;
326+
pod?: string;
327+
retries?: number;
317328
started?: string;
318329
terminated?: string;
319-
state?: TaskState;
320-
job?: string;
321-
report?: TaskReport;
330+
events?: TaskEvent[];
331+
errors?: TaskError[];
332+
activity?: string[];
333+
attached?: TaskAttachment[];
334+
}
335+
336+
export interface TaskPolicy {
337+
isolated?: boolean;
338+
preemptEnabled?: boolean;
339+
preemptExempt?: boolean;
340+
}
341+
342+
export interface TTL {
343+
created?: number;
344+
pending?: number;
345+
running?: number;
346+
succeeded?: number;
347+
failed?: number;
348+
}
349+
350+
export interface TaskEvent {
351+
kind: string;
352+
count: number;
353+
reason?: string;
354+
last: string; // time
355+
}
356+
357+
export interface TaskError {
358+
severity: string;
359+
description: string;
360+
}
361+
362+
export interface TaskAttachment {
363+
id: number;
364+
name?: string;
365+
activity?: number;
322366
}
323367

324368
export interface TaskData {
@@ -355,19 +399,6 @@ export interface TaskData {
355399
};
356400
}
357401

358-
interface TaskReport {
359-
activity: string[];
360-
completed: number;
361-
createTime: string;
362-
createUser: string;
363-
error: string;
364-
id: number;
365-
status: string;
366-
task: number;
367-
total: number;
368-
updateUser: string;
369-
}
370-
371402
export interface TaskgroupTask {
372403
name: string;
373404
data: any;

client/src/app/api/rest.ts

+3
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,9 @@ export function getTaskById(
345345
export const getTasks = () =>
346346
axios.get<Task[]>(TASKS).then((response) => response.data);
347347

348+
export const getServerTasks = (params: HubRequestParams = {}) =>
349+
getHubPaginatedResult<Task>(TASKS, params);
350+
348351
export const deleteTask = (id: number) => axios.delete<Task>(`${TASKS}/${id}`);
349352

350353
export const cancelTask = (id: number) =>

client/src/app/components/BreadCrumbPath.tsx

+6-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { Link } from "react-router-dom";
33
import { Breadcrumb, BreadcrumbItem, Button } from "@patternfly/react-core";
44

55
export interface BreadCrumbPathProps {
6-
breadcrumbs: { title: string; path: string | (() => void) }[];
6+
breadcrumbs: { title: string; path?: string | (() => void) }[];
77
}
88

99
export const BreadCrumbPath: React.FC<BreadCrumbPathProps> = ({
@@ -12,6 +12,11 @@ export const BreadCrumbPath: React.FC<BreadCrumbPathProps> = ({
1212
return (
1313
<Breadcrumb>
1414
{breadcrumbs.map((crumb, i, { length }) => {
15+
if (!crumb.path) {
16+
// the item is not a link
17+
return <BreadcrumbItem key={i}>{crumb.title}</BreadcrumbItem>;
18+
}
19+
1520
const isLast = i === length - 1;
1621

1722
const link =
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import React, { FormEvent, useState } from "react";
2+
3+
import {
4+
DatePicker,
5+
InputGroup,
6+
isValidDate as isValidJSDate,
7+
ToolbarChip,
8+
ToolbarChipGroup,
9+
ToolbarFilter,
10+
Tooltip,
11+
} from "@patternfly/react-core";
12+
13+
import { IFilterControlProps } from "./FilterControl";
14+
import {
15+
localizeInterval,
16+
americanDateFormat,
17+
isValidAmericanShortDate,
18+
isValidInterval,
19+
parseAmericanDate,
20+
parseInterval,
21+
toISODateInterval,
22+
} from "./dateUtils";
23+
24+
/**
25+
* This Filter type enables selecting an closed date range.
26+
* Precisely given range [A,B] a date X in the range if A <= X <= B.
27+
*
28+
* **Props are interpreted as follows**:<br>
29+
* 1) filterValue - date range encoded as ISO 8601 time interval string ("dateFrom/dateTo"). Only date part is used (no time).<br>
30+
* 2) setFilterValue - accepts the list of ranges.<br>
31+
*
32+
*/
33+
34+
export const DateRangeFilter = <TItem,>({
35+
category,
36+
filterValue,
37+
setFilterValue,
38+
showToolbarItem,
39+
isDisabled = false,
40+
}: React.PropsWithChildren<
41+
IFilterControlProps<TItem, string>
42+
>): JSX.Element | null => {
43+
const selectedFilters = filterValue ?? [];
44+
45+
const validFilters =
46+
selectedFilters?.filter((interval) =>
47+
isValidInterval(parseInterval(interval))
48+
) ?? [];
49+
const [from, setFrom] = useState<Date>();
50+
const [to, setTo] = useState<Date>();
51+
52+
const rangeToOption = (range: string) => {
53+
const [abbrRange, fullRange] = localizeInterval(range);
54+
return {
55+
key: range,
56+
node: (
57+
<Tooltip content={fullRange ?? range}>
58+
<span>{abbrRange ?? ""}</span>
59+
</Tooltip>
60+
),
61+
};
62+
};
63+
64+
const clearSingleRange = (
65+
category: string | ToolbarChipGroup,
66+
option: string | ToolbarChip
67+
) => {
68+
const target = (option as ToolbarChip)?.key;
69+
setFilterValue([...validFilters.filter((range) => range !== target)]);
70+
};
71+
72+
const onFromDateChange = (
73+
event: FormEvent<HTMLInputElement>,
74+
value: string
75+
) => {
76+
if (isValidAmericanShortDate(value)) {
77+
setFrom(parseAmericanDate(value));
78+
setTo(undefined);
79+
}
80+
};
81+
82+
const onToDateChange = (even: FormEvent<HTMLInputElement>, value: string) => {
83+
if (isValidAmericanShortDate(value)) {
84+
const newTo = parseAmericanDate(value);
85+
setTo(newTo);
86+
const target = toISODateInterval(from, newTo);
87+
if (target) {
88+
setFilterValue([
89+
...validFilters.filter((range) => range !== target),
90+
target,
91+
]);
92+
}
93+
}
94+
};
95+
96+
return (
97+
<ToolbarFilter
98+
key={category.categoryKey}
99+
chips={validFilters.map(rangeToOption)}
100+
deleteChip={clearSingleRange}
101+
deleteChipGroup={() => setFilterValue([])}
102+
categoryName={category.title}
103+
showToolbarItem={showToolbarItem}
104+
>
105+
<InputGroup>
106+
<DatePicker
107+
value={from ? americanDateFormat(from) : ""}
108+
dateFormat={americanDateFormat}
109+
dateParse={parseAmericanDate}
110+
onChange={onFromDateChange}
111+
aria-label="Interval start"
112+
placeholder="MM/DD/YYYY"
113+
// disable error text (no space in toolbar scenario)
114+
invalidFormatText={""}
115+
// default value ("parent") creates collision with sticky table header
116+
appendTo={document.body}
117+
isDisabled={isDisabled}
118+
/>
119+
<DatePicker
120+
value={to ? americanDateFormat(to) : ""}
121+
onChange={onToDateChange}
122+
isDisabled={isDisabled || !isValidJSDate(from)}
123+
dateFormat={americanDateFormat}
124+
dateParse={parseAmericanDate}
125+
// disable error text (no space in toolbar scenario)
126+
invalidFormatText={""}
127+
rangeStart={from}
128+
aria-label="Interval end"
129+
placeholder="MM/DD/YYYY"
130+
appendTo={document.body}
131+
/>
132+
</InputGroup>
133+
</ToolbarFilter>
134+
);
135+
};

client/src/app/components/FilterToolbar/FilterControl.tsx

+4
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
import { SelectFilterControl } from "./SelectFilterControl";
1212
import { SearchFilterControl } from "./SearchFilterControl";
1313
import { MultiselectFilterControl } from "./MultiselectFilterControl";
14+
import { DateRangeFilter } from "./DateRangeFilter";
1415

1516
export interface IFilterControlProps<TItem, TFilterCategoryKey extends string> {
1617
category: FilterCategory<TItem, TFilterCategoryKey>;
@@ -58,5 +59,8 @@ export const FilterControl = <TItem, TFilterCategoryKey extends string>({
5859
/>
5960
);
6061
}
62+
if (category.type === FilterType.dateRange) {
63+
return <DateRangeFilter category={category} {...props} />;
64+
}
6165
return null;
6266
};

client/src/app/components/FilterToolbar/FilterToolbar.tsx

+3-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export enum FilterType {
1818
multiselect = "multiselect",
1919
search = "search",
2020
numsearch = "numsearch",
21+
dateRange = "dateRange",
2122
}
2223

2324
export type FilterValue = string[] | undefined | null;
@@ -81,7 +82,8 @@ export interface ISearchFilterCategory<TItem, TFilterCategoryKey extends string>
8182
export type FilterCategory<TItem, TFilterCategoryKey extends string> =
8283
| IMultiselectFilterCategory<TItem, TFilterCategoryKey>
8384
| ISelectFilterCategory<TItem, TFilterCategoryKey>
84-
| ISearchFilterCategory<TItem, TFilterCategoryKey>;
85+
| ISearchFilterCategory<TItem, TFilterCategoryKey>
86+
| IBasicFilterCategory<TItem, TFilterCategoryKey>;
8587

8688
export type IFilterValues<TFilterCategoryKey extends string> = Partial<
8789
Record<TFilterCategoryKey, FilterValue>

0 commit comments

Comments
 (0)