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

Rebase of task-force from Orchid release branch. #1462

Merged
merged 74 commits into from
Oct 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
0f855f0
add check for multipart upload capability to UploadingJobsContext
JohnC-80 Aug 2, 2023
352d504
render message in UI that large source files will be split
JohnC-80 Aug 2, 2023
d8d8153
add multipart logic file with method for checking backend configuration
JohnC-80 Aug 2, 2023
38ffd89
add test for getStorageConfiguration
JohnC-80 Aug 2, 2023
622aeaa
add test for adding the configuration to the UplaodJobsContext
JohnC-80 Aug 2, 2023
4765bc2
clean up test file
JohnC-80 Aug 2, 2023
f1c9bf2
add mock for 'withOkapiKy' to stripes-core mock
JohnC-80 Aug 2, 2023
1887063
add optional chaining
JohnC-80 Aug 2, 2023
c7e2d3b
add logic to split files and upload to S3 storage
JohnC-80 Aug 4, 2023
ac32ad5
clean up
JohnC-80 Aug 4, 2023
1c43c61
add tests for UploadingJobsDisplay with object storage functionality
JohnC-80 Aug 4, 2023
cd6a49c
UI should be rendered regardless of canUseObjectStorage value
JohnC-80 Aug 4, 2023
e79fd2c
remove unnecessary comments/console
JohnC-80 Aug 7, 2023
1961213
UIDATIMP-1466, UIDATIMP-1464 Add composite job details to Job compone…
JohnC-80 Aug 18, 2023
35c6b65
UIDATIMP-1469 Handle cancelation of running composite job... (#1455)
JohnC-80 Aug 24, 2023
689ace8
UIDATIMP-1510 - render download link for slice of uploaded file. (#1456)
JohnC-80 Aug 24, 2023
3916584
UIDATIMP-1467 display jobParts column in Jobs using this profile sect…
JohnC-80 Aug 24, 2023
8f8120e
update file name with file key after multipart upload success
JohnC-80 Aug 25, 2023
91cb16c
move uploadDefinition update logic to multipart util, test function s…
JohnC-80 Aug 28, 2023
41c8e7a
convert multipart to a class, update UploadingJobsDisplay accordingly
JohnC-80 Aug 30, 2023
fe9dcce
clean leading numbers on filenames, progress calculation, download li…
JohnC-80 Aug 30, 2023
361c025
resolve NaN bug with progress meter for composite job
JohnC-80 Aug 30, 2023
53107e7
ensure that progress doesn't extend past 100%
JohnC-80 Aug 30, 2023
0c0a04e
add screen-reader message for running a job
JohnC-80 Sep 5, 2023
164ac64
ensure progress doesn't diminish
JohnC-80 Sep 5, 2023
5c2d442
lint
JohnC-80 Sep 5, 2023
34c82c1
add run job modal
JohnC-80 Sep 5, 2023
e5fdf7b
split progress calculation functions
JohnC-80 Sep 5, 2023
44463f1
fix problematic progress report
JohnC-80 Sep 6, 2023
7b48575
uncomment NaN check
JohnC-80 Sep 6, 2023
cd7b70f
implement upload cancelation button
JohnC-80 Sep 6, 2023
2fb93f2
remove timestamp from cancellable upload card
JohnC-80 Sep 6, 2023
4df49f2
multipart uploader class tests
JohnC-80 Sep 8, 2023
4e89953
refactor job cancelation
JohnC-80 Sep 8, 2023
d9aae3d
more multipart upload test progress
JohnC-80 Sep 8, 2023
a707c5b
fix error in mapFilesToUI
JohnC-80 Sep 12, 2023
105f05f
testing for cancellable upload
JohnC-80 Sep 12, 2023
de7a8b7
set resolution for @rehooks/local-storage
JohnC-80 Sep 12, 2023
dd522c3
clean up code smells, fix tests
JohnC-80 Sep 12, 2023
050922a
compositeJobStatus tests
JohnC-80 Sep 12, 2023
2ebc974
add composite job status tests
JohnC-80 Sep 12, 2023
6607bc8
slight refactor in multipartupload
JohnC-80 Sep 13, 2023
1d6ffe5
remove unnecessary bit of code
JohnC-80 Sep 13, 2023
6e475e4
split out manifest functions for viewAllLogs, add tests
JohnC-80 Sep 13, 2023
c3438fc
fix viewAllLogs tests
JohnC-80 Sep 13, 2023
7d54571
add jobParts to nonInteractive colums.
JohnC-80 Sep 13, 2023
d364d3e
check for UPLOADING-CANCELLABLE status to enable the navigation messa…
JohnC-80 Sep 14, 2023
e2372c3
add error handling for configuration status
JohnC-80 Sep 14, 2023
ef39127
translations for upload configuration error handling
JohnC-80 Sep 14, 2023
8dca23e
add formatted message to error callout
JohnC-80 Sep 14, 2023
fc1439c
fix bug with navigation modal for uploads
JohnC-80 Sep 14, 2023
e15faf5
fix lodash.get error on empty jobs
JohnC-80 Sep 14, 2023
56a78b4
include new totalRecordsInFile field from jobExecutions data.
JohnC-80 Sep 15, 2023
8d197a2
fix jobs progress tests
JohnC-80 Sep 15, 2023
f194a5b
include specific handling of xhr errors. Pass in intl context object.
JohnC-80 Sep 18, 2023
4623b56
wait for upload status before initializing jobExecutions data
JohnC-80 Sep 20, 2023
1df2de8
update multipartUpload test
JohnC-80 Sep 20, 2023
f00d1aa
update DataFetcher tests
JohnC-80 Sep 20, 2023
a410e08
conform functional paths in dataFetcher to depend on props vs resources.
JohnC-80 Sep 20, 2023
d90e474
back to depending on resources to get the URL
JohnC-80 Sep 20, 2023
faa88d3
update DataFetcher tests
JohnC-80 Sep 20, 2023
c768664
lint
JohnC-80 Sep 20, 2023
be03805
slight cleanup, handle missing download link with message
JohnC-80 Sep 26, 2023
e98f8f2
Merge branch 'task-force-merge-orchid' of https://github.com/folio-or…
JohnC-80 Sep 26, 2023
4e3542b
update/fix tests for SourceDownloadLink
JohnC-80 Sep 27, 2023
b03d5e4
update multipartUploader for handling multiple simultaneous uploads
JohnC-80 Sep 27, 2023
fdb8748
update tests for multipartUploader
JohnC-80 Sep 27, 2023
0dce212
fix code smell
JohnC-80 Oct 2, 2023
de4a09d
lint SourceDownloadLink test
JohnC-80 Oct 3, 2023
163661e
add new translation keys from master
JohnC-80 Oct 3, 2023
e9b16a7
add missing comma to translation file
JohnC-80 Oct 3, 2023
d236664
log changes
JohnC-80 Oct 4, 2023
2d5794b
whitespace
JohnC-80 Oct 4, 2023
52118a8
update mod-data-import interface version
JohnC-80 Oct 5, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
# Change history for ui-data-import

## **6.1.0** (in progress)
## **6.0.10** (in progress)
* Implement file upload to S3 (UIDATIMP-1460)
* Perform rough split on front-end for multipart Upload (UIDATIMP-1468)
* Notify users that large files will be split (UIDATIMP-1463)
* Retrieve backend configuration for splitting in UI (UIDATIMP-1462)
* Render details of composite jobs in on consolidated running job cards (UIDATIMP-1466)
* Display a link to download a slice from the automated splitting process (UIDATIMP-1510)
* Cancel upload/running a split job (UIDATIMP-1469)

### Features added:
* Landing page: Create hotlink from job profile name in log to the job profile details (UIDATIMP-1355)
Expand Down
10 changes: 7 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@folio/data-import",
"version": "6.0.9",
"version": "6.0.10",
"description": "Data Import manager",
"main": "src/index.js",
"repository": "folio-org/ui-data-import",
Expand All @@ -21,6 +21,9 @@
"build-mod-descriptor": "stripes mod descriptor --full --strict | jq '.[]' > module-descriptor.json ",
"formatjs-compile": "formatjs compile-folder --ast --format simple ./translations/ui-data-import ./translations/ui-data-import/compiled"
},
"jest": {
"testEnvironment": "node"
},
"devDependencies": {
"@babel/core": "^7.17.12",
"@babel/eslint-parser": "^7.17.0",
Expand Down Expand Up @@ -92,7 +95,8 @@
"redux": "^4.0.5"
},
"resolutions": {
"moment": "~2.29.4"
"moment": "~2.29.4",
"@rehooks/local-storage": "2.4.4"
},
"optionalDependencies": {
"@folio/plugin-find-organization": "^4.0.0"
Expand Down Expand Up @@ -182,7 +186,7 @@
}
],
"okapiInterfaces": {
"data-import": "3.0",
"data-import": "3.1",
"source-manager-job-executions": "3.0",
"data-import-converter-storage": "1.2"
},
Expand Down
71 changes: 61 additions & 10 deletions src/components/DataFetcher/DataFetcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
} from '../../utils';

import { DataFetcherContext } from '.';
import { requestConfiguration } from '../../utils/multipartUpload';

const {
RUNNING,
Expand Down Expand Up @@ -56,21 +57,42 @@ const logsUrlParams = [
const jobsUrl = createUrlFromArray('metadata-provider/jobExecutions', jobsUrlParams);
const logsUrl = createUrlFromArray('metadata-provider/jobExecutions', logsUrlParams);

const compositeJobsUrl = createUrlFromArray('metadata-provider/jobExecutions', [...jobsUrlParams, 'subordinationTypeNotAny=COMPOSITE_CHILD']);
const compositeLogsUrl = createUrlFromArray('metadata-provider/jobExecutions', [...logsUrlParams, 'subordinationTypeNotAny=COMPOSITE_PARENT']);

export function getJobSplittingURL(resources, splittingURL, nonSplitting) {
const { split_status: splitStatus } = resources;
if (!splitStatus?.isPending) {
if (splitStatus?.records[0]?.splitStatus) {
return splittingURL;
} else if (splitStatus?.records[0]?.splitStatus === false) {
return nonSplitting;
}
}
return undefined;
}

@stripesConnect
export class DataFetcher extends Component {
static manifest = Object.freeze({
jobs: {
type: 'okapi',
path: jobsUrl,
path: (_q, _p, resources) => getJobSplittingURL(resources, compositeJobsUrl, jobsUrl),
accumulate: true,
throwErrors: false,
},
logs: {
type: 'okapi',
path: logsUrl,
path: (_q, _p, resources) => getJobSplittingURL(resources, compositeLogsUrl, logsUrl),
accumulate: true,
throwErrors: false,
},
splitStatus: {
type: 'okapi',
path: requestConfiguration,
shouldRefreshRemote: () => false,
throwErrors: false,
},
});

static propTypes = {
Expand All @@ -93,29 +115,57 @@ export class DataFetcher extends Component {
PropTypes.shape({ jobExecutions: PropTypes.arrayOf(jobExecutionPropTypes).isRequired }),
).isRequired,
}),
splitStatus: PropTypes.shape({
hasLoaded: PropTypes.bool,
records: PropTypes.arrayOf(
PropTypes.shape({ splitStatus: PropTypes.bool.isRequired }),
).isRequired,
}),
}).isRequired,
updateInterval: PropTypes.number, // milliseconds
};

static defaultProps = { updateInterval: DEFAULT_FETCHER_UPDATE_INTERVAL };

state = {
statusLoaded: false,
contextData: { // eslint-disable-line object-curly-newline
hasLoaded: false,
},
};

async componentDidMount() {
componentDidMount() {
const { resources:{ splitStatus } } = this.props;
const { statusLoaded } = this.state;
this.mounted = true;
await this.fetchResourcesData(true);
this.updateResourcesData();
this.initialFetchPending = false;
if (!statusLoaded && splitStatus?.hasLoaded) {
this.setState({ statusLoaded: true }, () => {
if (!this.initialFetchPending) this.initialize();
});
}
}

componentDidUpdate(props, state) {
const { resources:{ splitStatus } } = props;
const { statusLoaded } = state;
if (!statusLoaded && splitStatus?.hasLoaded) {
this.setState({ statusLoaded: true }, () => {
if (!this.initialFetchPending) this.initialize();
});
}
}

componentWillUnmount() {
this.mounted = false;
clearTimeout(this.timeoutId);
}

initialize = async () => {
await this.fetchResourcesData(true);
this.updateResourcesData();
}

updateResourcesData() {
const { updateInterval } = this.props;

Expand All @@ -133,13 +183,14 @@ export class DataFetcher extends Component {
return;
}

const { mutator } = this.props;
const { mutator: { jobs, logs } } = this.props;

const fetchResourcesPromises = Object
.values(mutator)
.values({ jobs, logs })
.reduce((res, resourceMutator) => res.concat(this.fetchResourceData(resourceMutator)), []);

try {
this.initialFetchPending = true;
await Promise.all(fetchResourcesPromises);
this.mapResourcesToState();
} catch (error) {
Expand All @@ -163,12 +214,12 @@ export class DataFetcher extends Component {

/** @param {boolean} [isEmpty] flag to fill contextData with empty data */
mapResourcesToState(isEmpty) {
const { resources } = this.props;
const { resources: { jobs, logs } } = this.props;

const contextData = { hasLoaded: true };

forEach(resources, (resourceValue, resourceName) => {
contextData[resourceName] = isEmpty ? [] : get(resourceValue, ['records', 0, 'jobExecutions'], {});
forEach({ jobs, logs }, (resourceValue, resourceName) => {
contextData[resourceName] = isEmpty ? [] : get(resourceValue, ['records', 0, 'jobExecutions'], []);
});

this.setState({ contextData });
Expand Down
24 changes: 24 additions & 0 deletions src/components/DataFetcher/DataFetcher.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import '../../../test/jest/__mock__';
import {
DataFetcher,
DataFetcherContext,
getJobSplittingURL,
} from '.';

const reset = () => {};
Expand Down Expand Up @@ -52,6 +53,12 @@ const resources = buildResources({
fileName: 'testFileName',
}],
}],
otherResources: {
splitStatus: {
hasLoaded: true,
records: [{ splitStatus: true }]
}
}
});

const TestComponent = () => {
Expand Down Expand Up @@ -98,4 +105,21 @@ describe('DataFetcher component', () => {
await waitFor(() => expect(getByText('error')).toBeDefined());
});
});

describe('getJobSplittingURL', () => {
const trueResources = { split_status: { isPending: false, records: [{ splitStatus: true }] } };
const falseResources = { split_status: { isPending: false, records: [{ splitStatus: false }] } };
const pendingResources = { split_status: { isPending: true, records: [] } };
it('given a splitStatus of true, it provides the "trueUrl" parameter', () => {
expect(getJobSplittingURL(trueResources, 'trueUrl', 'falseUrl')).toBe('trueUrl');
});

it('given a splitStatus of true, it provides the "falseUrl" parameter', () => {
expect(getJobSplittingURL(falseResources, 'trueUrl', 'falseUrl')).toBe('falseUrl');
});

it('given a pending split status, it returns undefined', () => {
expect(getJobSplittingURL(pendingResources, 'trueUrl', 'falseUrl')).toBeUndefined();
});
});
});
4 changes: 2 additions & 2 deletions src/components/ImportJobs/ImportJobs.js
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ export class ImportJobs extends Component {

onDrop = async acceptedFiles => {
const { stripes: { okapi } } = this.props;
const { updateUploadDefinition } = this.context;
const { updateUploadDefinition, uploadConfiguration } = this.context;

const { url: host } = okapi;

Expand Down Expand Up @@ -175,7 +175,7 @@ export class ImportJobs extends Component {
return;
}

const files = API.mapFilesToUI(acceptedFiles);
const files = API.mapFilesToUI(acceptedFiles, uploadConfiguration?.canUseObjectStorage);

try {
// post file upload definition with all files metadata as
Expand Down
19 changes: 15 additions & 4 deletions src/components/JobLogsContainer/JobLogsContainer.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { useContext } from 'react';
import { useLocation } from 'react-router-dom';
import { useIntl } from 'react-intl';
import PropTypes from 'prop-types';
Expand All @@ -11,6 +11,7 @@ import {
stripesShape,
withStripes,
} from '@folio/stripes/core';
import { UploadingJobsContext } from '../UploadingJobsContextProvider/UploadingJobsContext';

import {
DEFAULT_JOB_LOG_COLUMNS_WIDTHS,
Expand Down Expand Up @@ -38,13 +39,21 @@ const JobLogsContainer = props => {
...rest
} = props;

const { formatMessage } = useIntl();
const { formatMessage, formatNumber } = useIntl();
const location = useLocation();

const { uploadConfiguration } = useContext(UploadingJobsContext);
const hasDeletePermission = stripes.hasPerm(permissions.DELETE_LOGS);

const getVisibleColumns = () => {
const baseColumns = [...DEFAULT_JOB_LOG_COLUMNS];
if (uploadConfiguration?.canUseObjectStorage) {
baseColumns.splice(3, 0, 'jobParts');
}
return hasDeletePermission ? ['selected', ...baseColumns] : baseColumns;
};

const customProperties = {
visibleColumns: hasDeletePermission ? ['selected', ...DEFAULT_JOB_LOG_COLUMNS] : DEFAULT_JOB_LOG_COLUMNS,
visibleColumns: getVisibleColumns(),
columnWidths: DEFAULT_JOB_LOG_COLUMNS_WIDTHS,
};

Expand All @@ -61,10 +70,12 @@ const JobLogsContainer = props => {
selectedRecords,
checkboxDisabled: checkboxesDisabled,
fieldsConfig,
formatNumber,
}),
fileName: record => fileNameCellFormatter(record, location),
status: statusCellFormatter(formatMessage),
jobProfileName: jobProfileNameCellFormatter,
jobParts: record => formatMessage({ id: 'ui-data-import.logViewer.partOfTotal' }, { number: record.jobPartNumber, total: record.totalJobParts }),
},
};

Expand Down
36 changes: 32 additions & 4 deletions src/components/JobLogsContainer/JobLogsContainer.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import '../../../test/jest/__mock__';
import JobLogsContainer from './JobLogsContainer';
import { FILE_STATUSES } from '../../utils';

import { UploadingJobsContext } from '../UploadingJobsContextProvider/UploadingJobsContext';

jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useLocation: () => ({
Expand All @@ -20,6 +22,13 @@ jest.mock('react-router-dom', () => ({
}),
}));

const splitRecord = {
status: FILE_STATUSES.COMMITTED,
progress: { current: 0 },
jobPartNumber: 2,
totalJobParts: 20
};

const successfulRecord = {
status: FILE_STATUSES.COMMITTED,
progress: { current: 0 },
Expand Down Expand Up @@ -50,8 +59,16 @@ const checkboxListProp = {
};

const stripes = buildStripes();
const defaultUploadContext = {
uploadConfiguration: { canUseObjectStorage: { splitStatus: false } }
};

const renderJobLogsContainer = record => {
const splittingUploadContext = {
uploadConfiguration: { canUseObjectStorage: { splitStatus: true } }
};

const renderJobLogsContainer = (record, context = defaultUploadContext) => {
const { Provider } = UploadingJobsContext;
const childComponent = listProps => {
listProps.resultsFormatter.status(record);
listProps.resultsFormatter.fileName(record);
Expand All @@ -60,14 +77,17 @@ const renderJobLogsContainer = record => {
<div>
<span>child component</span>
<span>{listProps.resultsFormatter.status(record)}</span>
{record.jobPartNumber && <span>{listProps.resultsFormatter.jobParts(record)}</span>}
</div>
);
};

const component = (
<JobLogsContainer checkboxList={checkboxListProp} stripes={stripes}>
{({ listProps }) => childComponent(listProps)}
</JobLogsContainer>
<Provider value={context}>
<JobLogsContainer checkboxList={checkboxListProp} stripes={stripes}>
{({ listProps }) => childComponent(listProps)}
</JobLogsContainer>
</Provider>
);

return renderWithIntl(component, translationsProperties);
Expand Down Expand Up @@ -117,4 +137,12 @@ describe('Job Logs container', () => {
expect(getByText('Stopped by user')).toBeDefined();
});
});

describe('when large file splitting is enabled, display job parts column', () => {
it('should render the correct parts current and total for the job', () => {
const { getByText } = renderJobLogsContainer(splitRecord, splittingUploadContext);
const { jobPartNumber, totalJobParts } = splitRecord;
expect(getByText(`${jobPartNumber} of ${totalJobParts}`)).toBeInTheDocument();
});
});
});
Loading