Skip to content

Commit

Permalink
UIEUS-302-harvester-logs (#388)
Browse files Browse the repository at this point in the history
* Add JobsView

* Add a test
  • Loading branch information
alb3rtino authored Oct 17, 2022
1 parent 20eb315 commit 7b0cc81
Show file tree
Hide file tree
Showing 13 changed files with 389 additions and 26 deletions.
18 changes: 15 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,8 @@
"custom-reports": "1.0",
"erm-usage/files": "1.0",
"usage-data-providers": "2.8",
"tags": "1.0"
"tags": "1.0",
"erm-usage-harvester": "1.3"
},
"permissionSets": [
{
Expand Down Expand Up @@ -164,7 +165,8 @@
"ui-erm-usage.udp.delete",
"ui-erm-usage.reports.delete",
"ui-erm-usage.generalSettings.manage",
"ui-erm-usage-harvester.start.single"
"ui-erm-usage-harvester.start.single",
"ui-erm-usage-harvester.jobs.view"
],
"visible": true
},
Expand All @@ -182,7 +184,8 @@
"aggregatorsettings.item.get",
"customreports.collection.get",
"customreports.item.get",
"erm-usage.files.item.get"
"erm-usage.files.item.get",
"ui-erm-usage-harvester.jobs.view"
],
"visible": true
},
Expand Down Expand Up @@ -254,6 +257,15 @@
"ermusageharvester.start.single"
],
"visible": true
},
{
"permissionName": "ui-erm-usage-harvester.jobs.view",
"displayName": "eUsage: Can view harvesting jobs",
"description": "Can view harvesting jobs",
"subPermissions": [
"ermusageharvester.jobs.get"
],
"visible": false
}
],
"icons": [
Expand Down
175 changes: 175 additions & 0 deletions src/components/JobsView/JobsView.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
import PropTypes from 'prop-types';
import {
Button,
MultiColumnList,
Pane,
PaneMenu,
Paneset,
} from '@folio/stripes-components';
import {
SearchAndSortQuery
} from '@folio/stripes-smart-components';
import { useIntl, FormattedMessage } from 'react-intl';
import { get } from 'lodash';
import { useStripes } from '@folio/stripes/core';
import { useHistory, useLocation } from 'react-router';
import { useState } from 'react';
import urls from '../../util/urls';

const JobsView = ({ source }) => {
const { formatMessage } = useIntl();
const stripes = useStripes();
const location = useLocation();
const history = useHistory();
const [fromPath] = useState(() => (location.state?.from ? location.state.from : urls.udps()));

const querySetter = ({ nsValues }) => {
source.mutator.query.update(nsValues);
};

const queryGetter = () => {
return get(source.resources, 'query', {});
};

const renderResultsPaneSubtitle = () => {
if (source && source.loaded()) {
const count = source.totalCount();
return (
<FormattedMessage
id="stripes-smart-components.searchResultsCountHeader"
values={{ count }}
/>
);
}
return <FormattedMessage id="stripes-smart-components.searchCriteria" />;
};

const getLocaleDate = (date) => {
return date
? new Date(date).toLocaleString(stripes.locale, {
timeZone: stripes.timezone,
})
: '';
};

const format = (number) => number.toLocaleString(stripes.locale, {
minimumIntegerDigits: 1,
maximumFractionDigits: 0,
});

const getDuration = (start, finish) => {
if (start && finish) {
const diff =
(new Date(finish).getTime() - new Date(start).getTime()) / 1000;
const hours = diff / 3600;
const minutes = (diff % 3600) / 60;
const seconds = diff % 60;

let result = '';
if (hours >= 1) {
result += format(hours) + 'h ';
}
result += format(minutes) + 'm ' + format(seconds) + 's';
return result;
} else {
return '';
}
};

const sortParam = source.resources.query.sort || '';
const sortDirection = sortParam.startsWith('-') ? 'descending' : 'ascending';
const sortOrder = sortParam.replace(/^-/, '').replace(/,.*/, '');

return (
<SearchAndSortQuery
initialFilterState={{ filters: [] }}
initialSearchState={{ query: '' }}
initialSortState={{ sort: '-startedAt' }}
queryGetter={queryGetter}
querySetter={querySetter}
syncToLocationSearch
>
{(renderProps) => (
<Paneset>
<Pane
defaultWidth="fill"
padContent={false}
paneTitle={formatMessage({ id: 'ui-erm-usage.harvester.jobs.paneTitle' })}
paneSub={renderResultsPaneSubtitle()}
lastMenu={
<PaneMenu>
<Button
buttonStyle="primary"
marginBottom0
onClick={() => {
source.mutator.timestamp.replace(Date.now());
}}
>
{formatMessage({ id: 'ui-erm-usage.harvester.jobs.refresh' })}
</Button>
</PaneMenu>
}
dismissible
onClose={() => history.push(fromPath)}
>
<MultiColumnList
autoSize
virtualize
loading={source.pending()}
visibleColumns={[
'providerId',
'type',
'startedAt',
'finishedAt',
'duration',
]}
formatter={{
providerId: (job) => {
if (job.providerId) {
const result = source.resources.udps.records.find(
(i) => i.id === job.providerId
);
return result ? result.label : job.providerId;
} else {
return stripes.okapi.tenant;
}
},
type: (job) => formatMessage({ id: 'ui-erm-usage.harvester.jobs.filter.type.' + job.type }),
startedAt: (job) => (job.nextStart
? getLocaleDate(job.nextStart)
: getLocaleDate(job.startedAt)),
finishedAt: (job) => getLocaleDate(job.finishedAt),
duration: (job) => getDuration(job.startedAt, job.finishedAt),
}}
contentData={source.records() || []}
columnMapping={{
providerId: formatMessage({ id: 'ui-erm-usage.harvester.jobs.column.provider' }),
type: formatMessage({ id: 'ui-erm-usage.harvester.jobs.column.type' }),
startedAt: formatMessage({ id: 'ui-erm-usage.harvester.jobs.column.start' }),
finishedAt: formatMessage({ id: 'ui-erm-usage.harvester.jobs.column.finish' }),
duration: formatMessage({ id: 'ui-erm-usage.harvester.jobs.column.duration' }),
}}
totalCount={source.totalCount() || 0}
onNeedMoreData={() => {
source.fetchMore(30);
}}
onHeaderClick={(e, m) => {
return ['startedAt', 'finishedAt'].includes(m.name)
? renderProps.onSort(e, m)
: {};
}}
sortDirection={sortDirection}
sortOrder={sortOrder}
/>
</Pane>
</Paneset>
)}
</SearchAndSortQuery>
);
};

JobsView.propTypes = {
source: PropTypes.object,
};

export default JobsView;
62 changes: 62 additions & 0 deletions src/components/JobsView/JobsView.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import React from 'react';
import '../../../test/jest/__mock__';
import { screen } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';

import renderWithIntl from '../../../test/jest/helpers/renderWithIntl';
import udpsFixture from '../../../test/fixtures/udps';
import jobsFixture from '../../../test/fixtures/jobs';
import JobsViewRoute from '../../routes/JobsViewRoute';

const renderJobView = (jobs) => renderWithIntl(
<MemoryRouter>
<JobsViewRoute
mutator={{
query: {
update: () => {},
},
}}
resources={{
udps: {
records: udpsFixture,
},
jobs: {
hasLoaded: true,
records: jobs,
other: {
totalRecords: jobs.length,
},
},
query: {
sort: '',
},
}}
/>
</MemoryRouter>
);


describe('JobView component', () => {
it('should display no results if no job data is provided', () => {
renderJobView([]);
expect(screen.queryByText('The list contains no items')).toBeInTheDocument();
});

it('should display properly formatted results if job data is provided', () => {
renderJobView(jobsFixture);

expect(screen.queryByText('The list contains no items')).toBeNull();

expect(screen.queryByText('American Chemical Society')).toBeInTheDocument();
expect(screen.queryByText('Provider', { exact: true })).toBeInTheDocument();
expect(screen.queryByText('9/28/2022, 11:33:03 AM')).toBeInTheDocument();
expect(screen.queryByText('9/28/2022, 11:33:04 AM')).toBeInTheDocument();
expect(screen.queryByText('0m 1s')).toBeInTheDocument();

expect(screen.queryByText('diku')).toBeInTheDocument();
expect(screen.queryByText('Tenant', { exact: true })).toBeInTheDocument();
expect(screen.queryByText('9/28/2022, 10:30:04 AM')).toBeInTheDocument();
expect(screen.queryByText('9/28/2022, 11:33:05 AM')).toBeInTheDocument();
expect(screen.queryByText('1h 3m 1s')).toBeInTheDocument();
});
});
1 change: 1 addition & 0 deletions src/components/JobsView/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './JobsView';
1 change: 1 addition & 0 deletions src/components/views/UDP.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ const renderUDP = (stripes) => {
statsReloadCount={0}
tagsEnabled={false}
udpReloadCount={0}
location
/>
</MemoryRouter>
</StripesContext.Provider>
Expand Down
61 changes: 38 additions & 23 deletions src/components/views/UDPs.js
Original file line number Diff line number Diff line change
Expand Up @@ -203,29 +203,41 @@ class UDPs extends React.Component {
return <FormattedMessage id="stripes-smart-components.searchCriteria" />;
};

renderResultsLastMenu() {
if (this.props.disableRecordCreation) {
return null;
}

renderActionMenu = () => {
const { intl, location } = this.props;
return (
<IfPermission perm="ui-erm-usage.udp.create">
<PaneMenu>
<FormattedMessage id="ui-erm-usage.udp.form.createUDP">
{(ariaLabel) => (
<Button
aria-label={ariaLabel}
buttonStyle="primary"
id="clickable-new-udp"
marginBottom0
to={`${urls.udpCreate()}${this.props.searchString}`}
>
<>
<div>
<IfPermission perm="ui-erm-usage.udp.create">
<Button
aria-label={intl.formatMessage({ id: 'ui-erm-usage.udp.form.createUDP' })}
buttonStyle="dropDownItem"
id="clickable-new-udp"
marginBottom0
to={`${urls.udpCreate()}${this.props.searchString}`}
>
<Icon icon="plus-sign">
<FormattedMessage id="stripes-smart-components.new" />
</Button>
)}
</FormattedMessage>
</PaneMenu>
</IfPermission>
</Icon>
</Button>
</IfPermission>
</div>
<div>
<IfPermission perm="ui-erm-usage-harvester.jobs.view">
<Button
aria-label={intl.formatMessage({ id: 'ui-erm-usage.harvester.jobs.show' })}
buttonStyle="dropDownItem"
id="clickable-harvester-logs"
marginBottom0
to={{ pathname: urls.jobsView, search: '?sort=-startedAt', state: { from: location.pathname + location.search } }}
>
<Icon icon="arrow-right">
<FormattedMessage id="ui-erm-usage.harvester.jobs.show" />
</Icon>
</Button>
</IfPermission>
</div>
</>
);
}

Expand Down Expand Up @@ -343,7 +355,7 @@ class UDPs extends React.Component {
appIcon={<AppIcon app="erm-usage" />}
defaultWidth="fill"
firstMenu={this.renderResultsFirstMenu(activeFilters)}
lastMenu={this.renderResultsLastMenu()}
actionMenu={this.renderActionMenu}
padContent={false}
paneTitle={
<FormattedMessage id="ui-erm-usage.usage-data-providers" />
Expand Down Expand Up @@ -389,8 +401,11 @@ UDPs.propTypes = Object.freeze({
children: PropTypes.object,
contentRef: PropTypes.object,
data: PropTypes.shape(),
disableRecordCreation: PropTypes.bool,
history: PropTypes.object.isRequired,
location: PropTypes.shape({
pathname: PropTypes.string.isRequired,
search: PropTypes.string.isRequired,
}).isRequired,
intl: PropTypes.object,
onNeedMoreData: PropTypes.func,
onSelectRow: PropTypes.func,
Expand Down
1 change: 1 addition & 0 deletions src/components/views/UDPs.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ const renderUDPs = (stripes, props = {}, udpsData, rerender) => renderWithIntl(
visibleColumns={['label', 'harvestingStatus', 'Latest statistics', 'aggregator']}
history={history}
onSearchComplete={onSearchComplete}
location
{...props}
/>
</ModuleHierarchyProvider>
Expand Down
Loading

0 comments on commit 7b0cc81

Please sign in to comment.