Added a page with jobs (#4258)
parent
bec253f022
commit
7f86a5d801
@ -0,0 +1,48 @@
|
|||||||
|
// Copyright (C) 2022 Intel Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
import { ActionUnion, createAction, ThunkAction } from 'utils/redux';
|
||||||
|
import getCore from 'cvat-core-wrapper';
|
||||||
|
import { JobsQuery } from 'reducers/interfaces';
|
||||||
|
|
||||||
|
const cvat = getCore();
|
||||||
|
|
||||||
|
export enum JobsActionTypes {
|
||||||
|
GET_JOBS = 'GET_JOBS',
|
||||||
|
GET_JOBS_SUCCESS = 'GET_JOBS_SUCCESS',
|
||||||
|
GET_JOBS_FAILED = 'GET_JOBS_FAILED',
|
||||||
|
}
|
||||||
|
|
||||||
|
interface JobsList extends Array<any> {
|
||||||
|
count: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const jobsActions = {
|
||||||
|
getJobs: (query: Partial<JobsQuery>) => createAction(JobsActionTypes.GET_JOBS, { query }),
|
||||||
|
getJobsSuccess: (jobs: JobsList, previews: string[]) => (
|
||||||
|
createAction(JobsActionTypes.GET_JOBS_SUCCESS, { jobs, previews })
|
||||||
|
),
|
||||||
|
getJobsFailed: (error: any) => createAction(JobsActionTypes.GET_JOBS_FAILED, { error }),
|
||||||
|
};
|
||||||
|
|
||||||
|
export type JobsActions = ActionUnion<typeof jobsActions>;
|
||||||
|
|
||||||
|
export const getJobsAsync = (query: JobsQuery): ThunkAction => async (dispatch) => {
|
||||||
|
try {
|
||||||
|
// Remove all keys with null values from the query
|
||||||
|
const filteredQuery: Partial<JobsQuery> = { ...query };
|
||||||
|
for (const [key, value] of Object.entries(filteredQuery)) {
|
||||||
|
if (value === null) {
|
||||||
|
delete filteredQuery[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch(jobsActions.getJobs(filteredQuery));
|
||||||
|
const jobs = await cvat.jobs.get(filteredQuery);
|
||||||
|
const previewPromises = jobs.map((job: any) => (job as any).frames.preview().catch(() => ''));
|
||||||
|
dispatch(jobsActions.getJobsSuccess(jobs, await Promise.all(previewPromises)));
|
||||||
|
} catch (error) {
|
||||||
|
dispatch(jobsActions.getJobsFailed(error));
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -0,0 +1,103 @@
|
|||||||
|
// Copyright (C) 2022 Intel Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { useHistory } from 'react-router';
|
||||||
|
import Card from 'antd/lib/card';
|
||||||
|
import Empty from 'antd/lib/empty';
|
||||||
|
import Descriptions from 'antd/lib/descriptions';
|
||||||
|
import { MoreOutlined } from '@ant-design/icons';
|
||||||
|
import Dropdown from 'antd/lib/dropdown';
|
||||||
|
import Menu from 'antd/lib/menu';
|
||||||
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||||
|
import { MenuInfo } from 'rc-menu/lib/interface';
|
||||||
|
|
||||||
|
import { useCardHeightHOC } from 'utils/hooks';
|
||||||
|
|
||||||
|
const useCardHeight = useCardHeightHOC({
|
||||||
|
containerClassName: 'cvat-jobs-page',
|
||||||
|
siblingClassNames: ['cvat-jobs-page-pagination', 'cvat-jobs-page-top-bar'],
|
||||||
|
paddings: 40,
|
||||||
|
numberOfRows: 3,
|
||||||
|
});
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
job: any;
|
||||||
|
preview: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function JobCardComponent(props: Props): JSX.Element {
|
||||||
|
const { job, preview } = props;
|
||||||
|
const [expanded, setExpanded] = useState<boolean>(false);
|
||||||
|
const history = useHistory();
|
||||||
|
const height = useCardHeight();
|
||||||
|
const onClick = (): void => {
|
||||||
|
history.push(`/tasks/${job.taskId}/jobs/${job.id}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
onMouseEnter={() => setExpanded(true)}
|
||||||
|
onMouseLeave={() => setExpanded(false)}
|
||||||
|
style={{ height }}
|
||||||
|
className='cvat-job-page-list-item'
|
||||||
|
cover={(
|
||||||
|
<>
|
||||||
|
{preview ? (
|
||||||
|
<img
|
||||||
|
className='cvat-jobs-page-job-item-card-preview'
|
||||||
|
src={preview}
|
||||||
|
alt='Preview'
|
||||||
|
onClick={onClick}
|
||||||
|
aria-hidden
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className='cvat-jobs-page-job-item-card-preview' onClick={onClick} aria-hidden>
|
||||||
|
<Empty description='Preview not found' />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className='cvat-job-page-list-item-id'>
|
||||||
|
ID:
|
||||||
|
{` ${job.id}`}
|
||||||
|
</div>
|
||||||
|
<div className='cvat-job-page-list-item-dimension'>{job.dimension.toUpperCase()}</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Descriptions column={1} size='small'>
|
||||||
|
<Descriptions.Item label='Stage'>{job.stage}</Descriptions.Item>
|
||||||
|
<Descriptions.Item label='State'>{job.state}</Descriptions.Item>
|
||||||
|
{ expanded ? (
|
||||||
|
<Descriptions.Item label='Size'>{job.stopFrame - job.startFrame + 1}</Descriptions.Item>
|
||||||
|
) : null}
|
||||||
|
{ expanded && job.assignee ? (
|
||||||
|
<Descriptions.Item label='Assignee'>{job.assignee.username}</Descriptions.Item>
|
||||||
|
) : null}
|
||||||
|
</Descriptions>
|
||||||
|
<Dropdown overlay={(
|
||||||
|
<Menu onClick={(action: MenuInfo) => {
|
||||||
|
if (action.key === 'task') {
|
||||||
|
history.push(`/tasks/${job.taskId}`);
|
||||||
|
} else if (action.key === 'project') {
|
||||||
|
history.push(`/projects/${job.projectId}`);
|
||||||
|
} else if (action.key === 'bug_tracker') {
|
||||||
|
// false alarm
|
||||||
|
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
||||||
|
window.open(job.bugTracker, '_blank', 'noopener noreferrer');
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Menu.Item key='task' disabled={job.taskId === null}>Go to the task</Menu.Item>
|
||||||
|
<Menu.Item key='project' disabled={job.projectId === null}>Go to the project</Menu.Item>
|
||||||
|
<Menu.Item key='bug_tracker' disabled={!job.bugTracker}>Go to the bug tracker</Menu.Item>
|
||||||
|
</Menu>
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<MoreOutlined className='cvat-job-card-more-button' />
|
||||||
|
</Dropdown>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default React.memo(JobCardComponent);
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
// Copyright (C) 2022 Intel Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import { Col, Row } from 'antd/lib/grid';
|
||||||
|
import { CombinedState } from 'reducers/interfaces';
|
||||||
|
import JobCard from './job-card';
|
||||||
|
|
||||||
|
function JobsContentComponent(): JSX.Element {
|
||||||
|
const jobs = useSelector((state: CombinedState) => state.jobs.current);
|
||||||
|
const previews = useSelector((state: CombinedState) => state.jobs.previews);
|
||||||
|
const dimensions = {
|
||||||
|
md: 22,
|
||||||
|
lg: 18,
|
||||||
|
xl: 16,
|
||||||
|
xxl: 16,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Row justify='center' align='middle'>
|
||||||
|
<Col className='cvat-jobs-page-list' {...dimensions}>
|
||||||
|
{jobs.map((job: any, idx: number): JSX.Element => (
|
||||||
|
<JobCard preview={previews[idx]} job={job} key={job.id} />
|
||||||
|
))}
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default React.memo(JobsContentComponent);
|
||||||
@ -0,0 +1,116 @@
|
|||||||
|
// Copyright (C) 2022 Intel Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
import './styles.scss';
|
||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import { useHistory } from 'react-router';
|
||||||
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
import Spin from 'antd/lib/spin';
|
||||||
|
import { Col, Row } from 'antd/lib/grid';
|
||||||
|
import Pagination from 'antd/lib/pagination';
|
||||||
|
import Empty from 'antd/lib/empty';
|
||||||
|
|
||||||
|
import { CombinedState } from 'reducers/interfaces';
|
||||||
|
import { getJobsAsync } from 'actions/jobs-actions';
|
||||||
|
|
||||||
|
import TopBarComponent from './top-bar';
|
||||||
|
import JobsContentComponent from './jobs-content';
|
||||||
|
|
||||||
|
function JobsPageComponent(): JSX.Element {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const query = useSelector((state: CombinedState) => state.jobs.query);
|
||||||
|
const fetching = useSelector((state: CombinedState) => state.jobs.fetching);
|
||||||
|
const count = useSelector((state: CombinedState) => state.jobs.count);
|
||||||
|
const history = useHistory();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// get relevant query parameters from the url and fetch jobs according to them
|
||||||
|
const { location } = history;
|
||||||
|
const searchParams = new URLSearchParams(location.search);
|
||||||
|
const copiedQuery = { ...query };
|
||||||
|
for (const key of Object.keys(copiedQuery)) {
|
||||||
|
if (searchParams.has(key)) {
|
||||||
|
const value = searchParams.get(key);
|
||||||
|
if (value) {
|
||||||
|
copiedQuery[key] = key === 'page' ? +value : value;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
copiedQuery[key] = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch(getJobsAsync(copiedQuery));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// when query is updated, set relevant search params to url
|
||||||
|
const searchParams = new URLSearchParams();
|
||||||
|
const { location } = history;
|
||||||
|
for (const [key, value] of Object.entries(query)) {
|
||||||
|
if (value) {
|
||||||
|
searchParams.set(key, value.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
history.push(`${location.pathname}?${searchParams.toString()}`);
|
||||||
|
}, [query]);
|
||||||
|
|
||||||
|
if (fetching) {
|
||||||
|
return (
|
||||||
|
<div className='cvat-jobs-page'>
|
||||||
|
<Spin size='large' className='cvat-spinner' />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const dimensions = {
|
||||||
|
md: 22,
|
||||||
|
lg: 18,
|
||||||
|
xl: 16,
|
||||||
|
xxl: 16,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='cvat-jobs-page'>
|
||||||
|
<TopBarComponent
|
||||||
|
query={query}
|
||||||
|
onChangeFilters={(filters: Record<string, string | null>) => {
|
||||||
|
dispatch(
|
||||||
|
getJobsAsync({
|
||||||
|
...query,
|
||||||
|
...filters,
|
||||||
|
page: 1,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{count ? (
|
||||||
|
<>
|
||||||
|
<JobsContentComponent />
|
||||||
|
<Row justify='space-around' about='middle'>
|
||||||
|
<Col {...dimensions}>
|
||||||
|
<Pagination
|
||||||
|
className='cvat-jobs-page-pagination'
|
||||||
|
onChange={(page: number) => {
|
||||||
|
dispatch(getJobsAsync({
|
||||||
|
...query,
|
||||||
|
page,
|
||||||
|
}));
|
||||||
|
}}
|
||||||
|
showSizeChanger={false}
|
||||||
|
total={count}
|
||||||
|
pageSize={12}
|
||||||
|
current={query.page}
|
||||||
|
showQuickJumper
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</>
|
||||||
|
) : <Empty />}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default React.memo(JobsPageComponent);
|
||||||
@ -0,0 +1,142 @@
|
|||||||
|
// Copyright (C) 2022 Intel Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
@import '../../base.scss';
|
||||||
|
|
||||||
|
.cvat-jobs-page {
|
||||||
|
padding-top: $grid-unit-size * 2;
|
||||||
|
padding-bottom: $grid-unit-size;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.cvat-jobs-page-top-bar {
|
||||||
|
> div:nth-child(1) {
|
||||||
|
> div:nth-child(1) {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
> div:nth-child(1) {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
span {
|
||||||
|
margin-right: $grid-unit-size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> div:nth-child(1) {
|
||||||
|
div > {
|
||||||
|
.cvat-title {
|
||||||
|
color: $text-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> div:nth-child(2) {
|
||||||
|
&.ant-empty {
|
||||||
|
position: absolute;
|
||||||
|
top: 40%;
|
||||||
|
left: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
padding-bottom: $grid-unit-size;
|
||||||
|
padding-top: $grid-unit-size;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cvat-job-page-list-item {
|
||||||
|
width: 25%;
|
||||||
|
border-width: $grid-unit-size / 2;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.ant-card-cover {
|
||||||
|
flex: 1;
|
||||||
|
height: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-card-body {
|
||||||
|
padding: $grid-unit-size;
|
||||||
|
|
||||||
|
.ant-descriptions-item {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.cvat-job-page-list-item-id {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cvat-job-page-list-item-dimension {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cvat-jobs-page-job-item-card-preview {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-around;
|
||||||
|
object-fit: cover;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cvat-job-page-list-item-dimension {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
margin: $grid-unit-size;
|
||||||
|
width: $grid-unit-size * 4;
|
||||||
|
background: white;
|
||||||
|
border-radius: 4px;
|
||||||
|
text-align: center;
|
||||||
|
opacity: 0.5;
|
||||||
|
padding: $grid-unit-size;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cvat-job-page-list-item-id {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
margin: $grid-unit-size $grid-unit-size $grid-unit-size 0;
|
||||||
|
width: fit-content;
|
||||||
|
background: white;
|
||||||
|
border-radius: 0 4px 4px 0;
|
||||||
|
padding: $grid-unit-size;
|
||||||
|
opacity: 0.5;
|
||||||
|
transition: 0.15s all ease;
|
||||||
|
box-shadow: $box-shadow-base;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cvat-jobs-page-pagination {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cvat-jobs-page-list {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cvat-job-card-more-button {
|
||||||
|
position: absolute;
|
||||||
|
bottom: $grid-unit-size * 2;
|
||||||
|
right: $grid-unit-size;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cvat-jobs-page-filters {
|
||||||
|
.ant-table-cell {
|
||||||
|
width: $grid-unit-size * 15;
|
||||||
|
background: #f0f2f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-table-tbody {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,104 @@
|
|||||||
|
// Copyright (C) 2022 Intel Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { Col, Row } from 'antd/lib/grid';
|
||||||
|
import Text from 'antd/lib/typography/Text';
|
||||||
|
import Table from 'antd/lib/table';
|
||||||
|
import { FilterValue, TablePaginationConfig } from 'antd/lib/table/interface';
|
||||||
|
import { JobsQuery } from 'reducers/interfaces';
|
||||||
|
import Input from 'antd/lib/input';
|
||||||
|
import Button from 'antd/lib/button';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
onChangeFilters(filters: Record<string, string | null>): void;
|
||||||
|
query: JobsQuery;
|
||||||
|
}
|
||||||
|
|
||||||
|
function TopBarComponent(props: Props): JSX.Element {
|
||||||
|
const { query, onChangeFilters } = props;
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: 'Stage',
|
||||||
|
dataIndex: 'stage',
|
||||||
|
key: 'stage',
|
||||||
|
filteredValue: query.stage?.split(',') || null,
|
||||||
|
className: `${query.stage ? 'cvat-jobs-page-filter cvat-jobs-page-filter-active' : 'cvat-jobs-page-filter'}`,
|
||||||
|
filters: [
|
||||||
|
{ text: 'annotation', value: 'annotation' },
|
||||||
|
{ text: 'validation', value: 'validation' },
|
||||||
|
{ text: 'acceptance', value: 'acceptance' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'State',
|
||||||
|
dataIndex: 'state',
|
||||||
|
key: 'state',
|
||||||
|
filteredValue: query.state?.split(',') || null,
|
||||||
|
className: `${query.state ? 'cvat-jobs-page-filter cvat-jobs-page-filter-active' : 'cvat-jobs-page-filter'}`,
|
||||||
|
filters: [
|
||||||
|
{ text: 'new', value: 'new' },
|
||||||
|
{ text: 'in progress', value: 'in progress' },
|
||||||
|
{ text: 'completed', value: 'completed' },
|
||||||
|
{ text: 'rejected', value: 'rejected' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Assignee',
|
||||||
|
dataIndex: 'assignee',
|
||||||
|
key: 'assignee',
|
||||||
|
filteredValue: query.assignee ? [query.assignee] : null,
|
||||||
|
className: `${query.assignee ? 'cvat-jobs-page-filter cvat-jobs-page-filter-active' : 'cvat-jobs-page-filter'}`,
|
||||||
|
filterDropdown: (
|
||||||
|
<div>
|
||||||
|
<Input.Search
|
||||||
|
defaultValue={query.assignee || ''}
|
||||||
|
placeholder='Filter by assignee'
|
||||||
|
onSearch={(value: string) => {
|
||||||
|
onChangeFilters({ assignee: value });
|
||||||
|
}}
|
||||||
|
enterButton
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type='link'
|
||||||
|
onClick={() => {
|
||||||
|
onChangeFilters({ assignee: null });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Reset
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Row className='cvat-jobs-page-top-bar' justify='center' align='middle'>
|
||||||
|
<Col md={22} lg={18} xl={16} xxl={16}>
|
||||||
|
<Row justify='space-between' align='bottom'>
|
||||||
|
<Col>
|
||||||
|
<Text className='cvat-title'>Jobs</Text>
|
||||||
|
</Col>
|
||||||
|
<Table
|
||||||
|
onChange={(_: TablePaginationConfig, filters: Record<string, FilterValue | null>) => {
|
||||||
|
const processed = Object.fromEntries(
|
||||||
|
Object.entries(filters)
|
||||||
|
.map(([key, values]) => (
|
||||||
|
[key, typeof values === 'string' || values === null ? values : values.join(',')]
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
onChangeFilters(processed);
|
||||||
|
}}
|
||||||
|
className='cvat-jobs-page-filters'
|
||||||
|
columns={columns}
|
||||||
|
size='small'
|
||||||
|
/>
|
||||||
|
</Row>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default React.memo(TopBarComponent);
|
||||||
@ -0,0 +1,52 @@
|
|||||||
|
// Copyright (C) 2022 Intel Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
import { JobsActions, JobsActionTypes } from 'actions/jobs-actions';
|
||||||
|
import { JobsState } from './interfaces';
|
||||||
|
|
||||||
|
const defaultState: JobsState = {
|
||||||
|
fetching: false,
|
||||||
|
count: 0,
|
||||||
|
query: {
|
||||||
|
page: 1,
|
||||||
|
state: null,
|
||||||
|
stage: null,
|
||||||
|
assignee: null,
|
||||||
|
},
|
||||||
|
current: [],
|
||||||
|
previews: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default (state: JobsState = defaultState, action: JobsActions): JobsState => {
|
||||||
|
switch (action.type) {
|
||||||
|
case JobsActionTypes.GET_JOBS: {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
fetching: true,
|
||||||
|
query: {
|
||||||
|
...defaultState.query,
|
||||||
|
...action.payload.query,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case JobsActionTypes.GET_JOBS_SUCCESS: {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
fetching: false,
|
||||||
|
count: action.payload.jobs.count,
|
||||||
|
current: action.payload.jobs,
|
||||||
|
previews: action.payload.previews,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case JobsActionTypes.GET_JOBS_FAILED: {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
fetching: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
Loading…
Reference in New Issue