diff --git a/client/src/components/pipelines/browser/data-storage/components/filter-config.js b/client/src/components/pipelines/browser/data-storage/components/filter-config.js
new file mode 100644
index 0000000000..30d523d214
--- /dev/null
+++ b/client/src/components/pipelines/browser/data-storage/components/filter-config.js
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2017-2024 EPAM Systems, Inc. (https://www.epam.com/)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import moment from 'moment-timezone';
+
+const FILTER_FIELDS = {
+ name: 'name',
+ sizeGreaterThan: 'sizeGreaterThan',
+ sizeLessThan: 'sizeLessThan',
+ dateAfter: 'dateAfter',
+ dateBefore: 'dateBefore',
+ dateFilterType: 'dateFilterType'
+};
+
+const PREDEFINED_DATE_FILTERS = [{
+ title: 'Last week',
+ key: 'lastWeek',
+ dateAfter: (currentDate) => currentDate && moment(currentDate).subtract(7, 'days').startOf('day'),
+ dateBefore: undefined
+}, {
+ title: 'Last month',
+ key: 'lastMonth',
+ dateAfter: (currentDate) => currentDate && moment(currentDate).subtract(1, 'month').endOf('day'),
+ dateBefore: undefined
+}];
+
+export {FILTER_FIELDS, PREDEFINED_DATE_FILTERS};
diff --git a/client/src/components/pipelines/browser/data-storage/components/filters.css b/client/src/components/pipelines/browser/data-storage/components/filters.css
new file mode 100644
index 0000000000..c805945bf6
--- /dev/null
+++ b/client/src/components/pipelines/browser/data-storage/components/filters.css
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2017-2024 EPAM Systems, Inc. (https://www.epam.com/)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+.filter-wrapper {
+ display: flex;
+ flex-direction: column;
+ padding: 5px;
+ outline: none;
+}
+
+.filter-wrapper-controls {
+ display: flex;
+ flex-wrap: nowrap;
+ justify-content: space-between;
+ margin: 5px 10px;
+ min-width: 180px;
+}
+
+.input-container {
+ margin-bottom: 5px;
+ display: flex;
+ flex-wrap: nowrap;
+ align-items: center;
+}
+
+.date-picker-container {
+ display: flex;
+ flex-wrap: nowrap;
+ align-items: center;
+ margin-bottom: 5px;
+}
diff --git a/client/src/components/pipelines/browser/data-storage/components/filters.js b/client/src/components/pipelines/browser/data-storage/components/filters.js
new file mode 100644
index 0000000000..1bf0a9f03d
--- /dev/null
+++ b/client/src/components/pipelines/browser/data-storage/components/filters.js
@@ -0,0 +1,454 @@
+/*
+ * Copyright 2017-2024 EPAM Systems, Inc. (https://www.epam.com/)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import PropTypes from 'prop-types';
+import {observer} from 'mobx-react';
+import moment from 'moment-timezone';
+import {computed} from 'mobx';
+import {
+ Radio,
+ InputNumber,
+ DatePicker,
+ Input
+} from 'antd';
+import {FILTER_FIELDS, PREDEFINED_DATE_FILTERS} from './filter-config';
+import styles from './filters.css';
+
+@observer
+class InputFilter extends React.Component {
+ static propTypes = {
+ storage: PropTypes.object,
+ hideFilterDropdown: PropTypes.func,
+ visible: PropTypes.bool,
+ label: PropTypes.string,
+ labelStyle: PropTypes.object,
+ placeholder: PropTypes.string
+ };
+
+ state = {
+ value: undefined
+ }
+
+ componentDidMount () {
+ this.rebuildState();
+ }
+
+ componentDidUpdate (prevProps) {
+ if (this.props.storage !== prevProps.storage || (this.props.visible && !prevProps.visible)) {
+ this.rebuildState();
+ }
+ }
+
+ @computed
+ get storage () {
+ return this.props.storage;
+ }
+
+ get filterKeyIsValid () {
+ const {filterKey} = this.props;
+ return filterKey && FILTER_FIELDS[filterKey];
+ }
+
+ get submitDisabled () {
+ const {submitDisabled} = this.props;
+ if (submitDisabled && typeof submitDisabled === 'function') {
+ return submitDisabled(this.state.value);
+ }
+ return false;
+ }
+
+ rebuildState = () => {
+ const {filterKey} = this.props;
+ if (this.filterKeyIsValid && this.storage?.currentFilter) {
+ this.setState({value: this.storage.currentFilter[filterKey]});
+ }
+ };
+
+ onChangeFilterState = event => {
+ this.setState({value: event.target.value});
+ };
+
+ onApplyFilter = () => {
+ const {hideFilterDropdown, filterKey} = this.props;
+ if (!this.filterKeyIsValid || this.submitDisabled) {
+ return;
+ }
+ this.storage.changeFilterField(
+ FILTER_FIELDS[filterKey],
+ this.state.value
+ );
+ hideFilterDropdown && hideFilterDropdown();
+ };
+
+ onClearFilter = () => {
+ const {hideFilterDropdown, filterKey} = this.props;
+ if (!this.filterKeyIsValid) {
+ return;
+ }
+ this.storage.changeFilter({
+ [FILTER_FIELDS[filterKey]]: undefined
+ });
+ hideFilterDropdown && hideFilterDropdown();
+ };
+
+ render () {
+ const {
+ label,
+ labelStyle,
+ placeholder
+ } = this.props;
+ return (
+
+ );
+ }
+}
+@observer
+class SizeFilter extends React.Component {
+ static propTypes = {
+ storage: PropTypes.object,
+ hideFilterDropdown: PropTypes.func,
+ visible: PropTypes.bool
+ };
+
+ state = {
+ [FILTER_FIELDS.sizeGreaterThan]: undefined,
+ [FILTER_FIELDS.sizeLessThan]: undefined
+ }
+
+ componentDidMount () {
+ this.rebuildState();
+ }
+
+ componentDidUpdate (prevProps) {
+ if (this.props.storage !== prevProps.storage || (this.props.visible && !prevProps.visible)) {
+ this.rebuildState();
+ }
+ }
+
+ @computed
+ get storage () {
+ return this.props.storage;
+ }
+
+ rebuildState = () => {
+ if (!this.storage?.currentFilter) {
+ return;
+ }
+ const from = this.storage.currentFilter[FILTER_FIELDS.sizeGreaterThan];
+ const to = this.storage.currentFilter[FILTER_FIELDS.sizeLessThan];
+ this.setState({
+ [FILTER_FIELDS.sizeGreaterThan]: from,
+ [FILTER_FIELDS.sizeLessThan]: to
+ });
+ };
+
+ onChangeFilterState = key => value => {
+ this.setState({[key]: value});
+ };
+
+ onApplyFilter = () => {
+ const {hideFilterDropdown} = this.props;
+ const from = this.state[FILTER_FIELDS.sizeGreaterThan];
+ const to = this.state[FILTER_FIELDS.sizeLessThan];
+ this.storage.changeFilter({
+ [FILTER_FIELDS.sizeGreaterThan]: from,
+ [FILTER_FIELDS.sizeLessThan]: to
+ });
+ hideFilterDropdown && hideFilterDropdown();
+ };
+
+ onClearFilter = () => {
+ const {hideFilterDropdown} = this.props;
+ this.storage.changeFilter({
+ [FILTER_FIELDS.sizeGreaterThan]: undefined,
+ [FILTER_FIELDS.sizeLessThan]: undefined
+ });
+ hideFilterDropdown && hideFilterDropdown();
+ };
+
+ onKeyDown = (e) => {
+ if (e.key.toLowerCase() === 'enter') {
+ this.onApplyFilter();
+ }
+ };
+
+ render () {
+ if (!this.storage) {
+ return null;
+ }
+ return (
+
+
+ From:
+
+ Mb
+
+
+ To:
+
+ Mb
+
+
+
+ );
+ }
+}
+
+@observer
+class DateFilter extends React.Component {
+ static propTypes = {
+ storage: PropTypes.object,
+ hideFilterDropdown: PropTypes.func,
+ visible: PropTypes.bool
+ };
+
+ state = {
+ [FILTER_FIELDS.dateFilterType]: 'datePicker',
+ [FILTER_FIELDS.dateAfter]: undefined,
+ [FILTER_FIELDS.dateBefore]: undefined
+ }
+
+ containerRef;
+
+ componentDidMount () {
+ this.rebuildState();
+ }
+
+ componentDidUpdate (prevProps) {
+ if (this.props.storage !== prevProps.storage || (this.props.visible && !prevProps.visible)) {
+ this.rebuildState();
+ }
+ }
+
+ @computed
+ get storage () {
+ return this.props.storage;
+ }
+
+ get predefinedDateFilters () {
+ return PREDEFINED_DATE_FILTERS.map((filter) => ({
+ title: filter.title,
+ key: filter.key,
+ dateAfter: filter.dateAfter || undefined,
+ dateBefore: filter.dateBefore || undefined
+ }));
+ }
+
+ rebuildState = () => {
+ if (!this.storage?.currentFilter) {
+ return;
+ }
+ const dateFilterType = this.storage.currentFilter[FILTER_FIELDS.dateFilterType] || 'datePicker';
+ const from = this.storage.currentFilter[FILTER_FIELDS.dateAfter];
+ const to = this.storage.currentFilter[FILTER_FIELDS.dateBefore];
+ this.setState({
+ [FILTER_FIELDS.dateFilterType]: dateFilterType,
+ [FILTER_FIELDS.dateAfter]: from,
+ [FILTER_FIELDS.dateBefore]: to
+ });
+ };
+
+ onApplyFilter = () => {
+ const {hideFilterDropdown} = this.props;
+ const dateFilterType = this.state[FILTER_FIELDS.dateFilterType];
+ const from = this.state[FILTER_FIELDS.dateAfter];
+ const to = this.state[FILTER_FIELDS.dateBefore];
+ this.storage.changeFilter({
+ [FILTER_FIELDS.dateFilterType]: dateFilterType,
+ [FILTER_FIELDS.dateAfter]: from,
+ [FILTER_FIELDS.dateBefore]: to
+ });
+ hideFilterDropdown && hideFilterDropdown();
+ };
+
+ onClearFilter = () => {
+ const {hideFilterDropdown} = this.props;
+ this.storage.changeFilter({
+ [FILTER_FIELDS.dateFilterType]: 'datePicker',
+ [FILTER_FIELDS.dateAfter]: undefined,
+ [FILTER_FIELDS.dateBefore]: undefined
+ });
+ hideFilterDropdown && hideFilterDropdown();
+ };
+
+ onChangeRadio = (event) => {
+ this.setState({dateFilterType: event.target.value}, () => {
+ const type = event.target.value;
+ const predefined = PREDEFINED_DATE_FILTERS.find(({key}) => key === type);
+ const currentDate = moment();
+ const from = typeof predefined?.dateAfter === 'function'
+ ? predefined.dateAfter(currentDate)
+ : undefined;
+ const to = typeof predefined?.dateBefore === 'function'
+ ? predefined.dateBefore(currentDate)
+ : undefined;
+ this.setState({
+ [FILTER_FIELDS.dateAfter]: from,
+ [FILTER_FIELDS.dateBefore]: to
+ });
+ });
+ };
+
+ onChangeFrom = (date) => {
+ let dateString;
+ if (date) {
+ dateString = moment(date).startOf('d');
+ }
+ this.setState({[FILTER_FIELDS.dateAfter]: dateString});
+ };
+
+ onChangeTo = (date) => {
+ let dateString;
+ if (date) {
+ dateString = moment(date).endOf('d');
+ }
+ this.setState({[FILTER_FIELDS.dateBefore]: dateString});
+ };
+
+ onKeyDown = event => {
+ if (event.key.toLowerCase() === 'enter') {
+ this.onApplyFilter();
+ }
+ }
+
+ onOpenChange = (visible) => {
+ if (!visible) {
+ this.containerRef && this.containerRef.focus();
+ }
+ };
+
+ renderPicker = () => {
+ const {dateFilterType} = this.state;
+ if (dateFilterType !== 'datePicker') {
+ return null;
+ }
+ return (
+
+
+ From:
+ node.parentNode}
+ onChange={this.onChangeFrom}
+ value={this.state[FILTER_FIELDS.dateAfter]}
+ onOpenChange={this.onOpenChange}
+ />
+
+
+ To:
+ node.parentNode}
+ onChange={this.onChangeTo}
+ onOpenChange={this.onOpenChange}
+ value={this.state[FILTER_FIELDS.dateBefore]}
+ />
+
+
+ );
+ };
+
+ render () {
+ if (!this.storage) {
+ return null;
+ }
+ return (
+ {
+ this.containerRef = el;
+ }}
+ onKeyDown={this.onKeyDown}
+ className={styles.filterWrapper}
+ tabIndex={-1}
+ >
+
+ {this.predefinedDateFilters.map(filter => (
+ {filter.title}
+ ))}
+
+ Custom
+
+
+ {this.renderPicker()}
+
+
+ );
+ }
+}
+
+function FilterFooter ({onOk, onClear, okDisabled}) {
+ const handleOk = () => !okDisabled && onOk && onOk();
+ const handleClear = () => onClear && onClear();
+ return (
+
+ );
+}
+
+FilterFooter.propTypes = {
+ onOk: PropTypes.func,
+ onClear: PropTypes.func,
+ okDisabled: PropTypes.bool
+};
+
+export {SizeFilter, DateFilter, InputFilter, FILTER_FIELDS};
diff --git a/client/src/components/pipelines/browser/data-storage/components/storage-pagination.js b/client/src/components/pipelines/browser/data-storage/components/storage-pagination.js
index d63b59e8b4..cbd5b722e4 100644
--- a/client/src/components/pipelines/browser/data-storage/components/storage-pagination.js
+++ b/client/src/components/pipelines/browser/data-storage/components/storage-pagination.js
@@ -21,7 +21,7 @@ import classNames from 'classnames';
import styles from './storage-pagination.css';
function StoragePagination ({className, style, storage}) {
- if (!storage || !storage.infoLoaded) {
+ if (!storage || !storage.infoLoaded || storage.filtersApplied) {
return null;
}
return (
diff --git a/client/src/components/pipelines/browser/data-storage/index.js b/client/src/components/pipelines/browser/data-storage/index.js
index 8e6abb2afe..2d41002eff 100644
--- a/client/src/components/pipelines/browser/data-storage/index.js
+++ b/client/src/components/pipelines/browser/data-storage/index.js
@@ -86,6 +86,7 @@ import {
METADATA_KEY as REQUEST_DAV_ACCESS_ATTRIBUTE
} from '../../../special/metadata/special/request-dav-access';
import StorageSize from '../../../special/storage-size';
+import highlightText from '../../../special/highlightText';
import {extractFileShareMountList} from '../forms/DataStoragePathInput';
import SharedItemInfo from '../forms/data-storage-item-sharing/SharedItemInfo';
import {SAMPLE_SHEET_FILE_NAME_REGEXP} from '../../../special/sample-sheet/utilities';
@@ -101,6 +102,7 @@ import StorageSharedLinkButton from './components/storage-shared-link-button';
import DownloadFileButton from './components/download-file-button';
import handleDownloadItems from '../../../special/download-storage-items';
import styles from '../Browser.css';
+import {SizeFilter, DateFilter, InputFilter, FILTER_FIELDS} from './components/filters';
const STORAGE_CLASSES = {
standard: 'STANDARD',
@@ -177,6 +179,7 @@ export default class DataStorage extends React.Component {
});
@observable generateDownloadUrls;
+ @observable filterDropdownVisible;
get showMetadata () {
if (this.state.metadata === undefined && this.storage.info) {
@@ -1476,9 +1479,18 @@ export default class DataStorage extends React.Component {
);
};
+ const filteredStatus = (keys = []) => {
+ const filtered = keys.some(key => !!this.storage.currentFilter?.[key]);
+ return {
+ filtered,
+ filteredValue: filtered ? ['filtered'] : null
+ };
+ };
+ const hideFilterDropdown = () => {
+ this.filterDropdownVisible = undefined;
+ };
const selectionColumn = {
key: 'selection',
- title: '',
className: (this.showVersions || hasVersions)
? styles.checkboxCellVersions
: styles.checkboxCell,
@@ -1588,11 +1600,30 @@ export default class DataStorage extends React.Component {
title: 'Name',
className: styles.nameCell,
render: (text, item) => {
+ const search = this.storage.currentFilter[FILTER_FIELDS.name];
+ const highlightedText = this.storage.filtersApplied && search
+ ? highlightText(text, search)
+ : text;
if (item.latest) {
- return `${text} (latest)`;
+ return `${highlightedText} (latest)`;
}
- return text;
+ return highlightedText;
+ },
+ filterDropdown: (
+ (value || '').length < 3}
+ />
+ ),
+ filterDropdownVisible: this.filterDropdownVisible === 'name',
+ onFilterDropdownVisibleChange: (visible) => {
+ this.filterDropdownVisible = visible ? 'name' : undefined;
},
+ ...filteredStatus([FILTER_FIELDS.name]),
onCellClick: (item) => this.didSelectDataStorageItem(item)
};
const sizeColumn = {
@@ -1601,6 +1632,18 @@ export default class DataStorage extends React.Component {
title: 'Size',
className: styles.sizeCell,
render: size => displaySize(size),
+ filterDropdown: (
+
+ ),
+ filterDropdownVisible: this.filterDropdownVisible === 'size',
+ onFilterDropdownVisibleChange: (visible) => {
+ this.filterDropdownVisible = visible ? 'size' : undefined;
+ },
+ ...filteredStatus([FILTER_FIELDS.sizeGreaterThan, FILTER_FIELDS.sizeLessThan]),
onCellClick: (item) => this.didSelectDataStorageItem(item)
};
const changedColumn = {
@@ -1609,6 +1652,18 @@ export default class DataStorage extends React.Component {
title: 'Date changed',
className: styles.changedCell,
render: (date) => date ? displayDate(date) : '',
+ filterDropdown: (
+
+ ),
+ filterDropdownVisible: this.filterDropdownVisible === 'date',
+ onFilterDropdownVisibleChange: (visible) => {
+ this.filterDropdownVisible = visible ? 'date' : undefined;
+ },
+ ...filteredStatus([FILTER_FIELDS.dateAfter, FILTER_FIELDS.dateBefore]),
onCellClick: (item) => this.didSelectDataStorageItem(item)
};
const labelsColumn = {
@@ -1627,6 +1682,19 @@ export default class DataStorage extends React.Component {
const actionsColumn = {
key: 'actions',
className: styles.itemActions,
+ title: (
+
+ {this.storage.resultsFiltered ? (
+
+ ) : null}
+
+ ),
render: this.actionsRenderer
};
@@ -2534,6 +2602,14 @@ export default class DataStorage extends React.Component {
navigate={this.navigate}
navigateFull={this.navigateFull} />
+ {this.storage.resultsFilteredAndTruncated ? (
+
+ ) : null}
{
this.renderContent()
}
@@ -2833,12 +2909,15 @@ export default class DataStorage extends React.Component {
}
updateStorageIfRequired = () => {
- this.storage.initialize(
+ const changed = this.storage.initialize(
this.props.storageId,
this.props.path,
this.props.showVersions,
this.props.showArchives
);
+ if (changed) {
+ this.storage.resetFilter();
+ }
};
clearSelectedItemsIfRequired = () => {
diff --git a/client/src/models/dataStorage/DataStorageFilter.js b/client/src/models/dataStorage/DataStorageFilter.js
new file mode 100644
index 0000000000..35635bac31
--- /dev/null
+++ b/client/src/models/dataStorage/DataStorageFilter.js
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2017-2024 EPAM Systems, Inc. (https://www.epam.com/)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import RemotePost from '../basic/RemotePost';
+
+export default class DataStorageFilter extends RemotePost {
+ constructor (
+ id,
+ path,
+ showVersion = false,
+ showArchives = false
+ ) {
+ super();
+ const query = [
+ !!path && `path=${encodeURIComponent(path)}`,
+ `showVersion=${!!showVersion}`,
+ `showArchived=${!!showArchives}`
+ ]
+ .filter(Boolean)
+ .join('&');
+ this.url = `/datastorage/${id}/list/filter?${query}`;
+ }
+}
diff --git a/client/src/models/dataStorage/data-storage-listing.js b/client/src/models/dataStorage/data-storage-listing.js
index 8dc612764b..97209a4e8e 100644
--- a/client/src/models/dataStorage/data-storage-listing.js
+++ b/client/src/models/dataStorage/data-storage-listing.js
@@ -15,16 +15,25 @@
*/
import {action, computed, observable} from 'mobx';
+import moment from 'moment-timezone';
import dataStorages from './DataStorages';
import preferences from '../preferences/PreferencesLoad';
import authenticatedUserInfo from '../user/WhoAmI';
import DataStorageRequest from './DataStoragePage';
+import DataStorageFilter from './DataStorageFilter';
import roleModel from '../../utils/roleModel';
import MetadataLoad from '../metadata/MetadataLoad';
const DEFAULT_DELIMITER = '/';
const PAGE_SIZE = 40;
+const mbToBytes = mb => {
+ if (isNaN(mb)) {
+ return;
+ }
+ return Math.round(mb * (1024 ** 2));
+};
+
/**
* Returns true if user is allowed to download from storage according to the
* `download.enabled` attribute value
@@ -268,6 +277,21 @@ class DataStorageListing {
@observable pagePath;
@observable downloadEnabled = false;
+ /**
+ * Filters info.
+ * Request results may be truncated.
+ */
+ @observable filters = {
+ name: undefined,
+ sizeGreaterThan: undefined,
+ sizeLessThan: undefined,
+ dateFilterType: undefined,
+ dateAfter: undefined,
+ dateBefore: undefined
+ };
+ @observable resultsTruncated = false;
+ @observable filtersApplied = false;
+
/**
* @param {DataStoragePagesOptions} options
*/
@@ -363,6 +387,30 @@ class DataStorageListing {
};
}
+ @computed
+ get currentFilter () {
+ return this.filters;
+ }
+
+ @computed
+ get filtersEmpty () {
+ if (!this.currentFilter) {
+ return true;
+ }
+ return Object.values(this.currentFilter)
+ .every(value => value === undefined);
+ }
+
+ @computed
+ get resultsFiltered () {
+ return this.filtersApplied && !this.filtersEmpty;
+ }
+
+ @computed
+ get resultsFilteredAndTruncated () {
+ return this.resultsFiltered && this.resultsTruncated;
+ }
+
_increaseUniqueToken = () => {
this.token = (this.token || 0) + 1;
return this.token;
@@ -387,6 +435,21 @@ class DataStorageListing {
this.markers = resetMarkersForPath();
};
+ @action
+ resetFilter = (silent = true) => {
+ this.filters = {
+ name: undefined,
+ sizeGreaterThan: undefined,
+ sizeLessThan: undefined,
+ dateFilterType: undefined,
+ dateAfter: undefined,
+ dateBefore: undefined
+ };
+ if (!silent) {
+ this.refreshCurrentPath(true);
+ }
+ };
+
@action
initialize = (
storageId,
@@ -531,6 +594,80 @@ class DataStorageListing {
return true;
};
+ @action
+ changeFilterField = (key, value, applyChanges = true) => {
+ this.currentFilter[key] = value;
+ if (applyChanges) {
+ this.applyFilters();
+ }
+ };
+
+ @action
+ changeFilter = (newFilterObj = {}, applyChanges = true) => {
+ Object.keys(newFilterObj).forEach(key => {
+ this.changeFilterField(key, newFilterObj[key], false);
+ });
+ if (applyChanges) {
+ this.applyFilters();
+ }
+ };
+
+ @action
+ applyFilters = async () => {
+ const pathCorrected = correctPath(
+ this.path,
+ {
+ leadingSlash: false,
+ trailingSlash: false,
+ undefinedAsEmpty: true
+ }
+ );
+ const formatToUTCString = date => date
+ ? moment.utc(date).format('YYYY-MM-DD HH:mm:ss.SSS')
+ : undefined;
+ try {
+ const request = new DataStorageFilter(
+ this.storageId,
+ pathCorrected ? decodeURIComponent(pathCorrected) : undefined
+ );
+ let payload = {
+ nameFilter: this.currentFilter?.name,
+ sizeGreaterThan: mbToBytes(this.currentFilter?.sizeGreaterThan),
+ sizeLessThan: mbToBytes(this.currentFilter?.sizeLessThan),
+ dateAfter: formatToUTCString(this.currentFilter?.dateAfter),
+ dateBefore: formatToUTCString(this.currentFilter?.dateBefore)
+ };
+ payload = Object.fromEntries(Object.entries(payload)
+ .filter(([_, value]) => value !== undefined)
+ );
+ if (!Object.keys(payload).length) {
+ return this.refreshCurrentPath(true);
+ }
+ await request.send(payload);
+ if (request.error) {
+ throw new Error(request.error);
+ }
+ if (!request.loaded) {
+ throw new Error('Error loading page');
+ }
+ const {results = [], nextPageMarker} = request.value || {};
+ this.resultsTruncated = !!nextPageMarker;
+ this.filtersApplied = true;
+ this.pageElements = results;
+ this.pageLoaded = true;
+ this.pagePath = pathCorrected;
+ } catch (error) {
+ this.pageElements = [];
+ this.pageError = error.message;
+ this.pageLoaded = false;
+ this.pagePath = pathCorrected;
+ } finally {
+ this.pagePending = false;
+ this.pageLoaded = !this.pageError;
+ this.filtersApplied = true;
+ }
+ };
+
@action
fetchCurrentPage = async () => {
const token = this._increaseUniqueToken();
@@ -575,6 +712,7 @@ class DataStorageListing {
this.pageLoaded = true;
this.pagePath = pathCorrected;
this.markers = insertNextPageMarker(this.path, nextPageMarker, this.markers);
+ this.filtersApplied = false;
});
} catch (error) {
submitChanges(() => {
@@ -587,6 +725,7 @@ class DataStorageListing {
submitChanges(() => {
this.pagePending = false;
this.pageLoaded = !this.pageError;
+ this.filtersApplied = false;
});
}
};