Skip to content

feat: Allow editing filter without loading data in data browser #2949

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

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
168 changes: 148 additions & 20 deletions src/components/BrowserFilter/BrowserFilter.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,21 +47,63 @@ export default class BrowserFilter extends React.Component {
this.wrapRef = React.createRef();
}

getClassNameFromURL() {
const pathParts = window.location.pathname.split('/');
const browserIndex = pathParts.indexOf('browser');
return browserIndex >= 0 && pathParts[browserIndex + 1]
? pathParts[browserIndex + 1]
: this.props.className;
}

initializeEditFilterMode() {
const urlParams = new URLSearchParams(window.location.search);
const isEditFilterMode = urlParams.get('editFilter') === 'true';

if (isEditFilterMode && !this.state.open) {
const currentFilter = this.getCurrentFilterInfo();
let filtersToDisplay = this.props.filters;
if (this.props.filters.size === 0) {
filtersToDisplay = this.loadFiltersFromURL();
}

const filters = this.convertDatesForDisplay(filtersToDisplay);
this.setState({
open: true,
showMore: true,
filters: filters,
editMode: true,
name: currentFilter.name || '',
originalFilterName: currentFilter.name || '',
relativeDates: currentFilter.hasRelativeDates || false,
originalRelativeDates: currentFilter.hasRelativeDates || false,
originalFilters: filtersToDisplay,
});
}
}

componentWillReceiveProps(props) {
if (props.className !== this.props.className) {
this.setState({ open: false });
}

this.initializeEditFilterMode();
}

componentDidMount() {
this.initializeEditFilterMode();
}

isCurrentFilterSaved() {
// First check if there's a filterId in the URL (means we're definitely viewing a saved filter)
const urlParams = new URLSearchParams(window.location.search);
const filterId = urlParams.get('filterId');

const urlClassName = this.getClassNameFromURL();

if (filterId) {
const preferences = ClassPreferences.getPreferences(
this.context.applicationId,
this.props.className
urlClassName
);

if (preferences.filters) {
Expand All @@ -77,36 +119,44 @@ export default class BrowserFilter extends React.Component {

// Check for legacy filters (filters parameter without filterId)
const filtersParam = urlParams.get('filters');
if (filtersParam && this.props.filters.size > 0) {
if (filtersParam) {
const preferences = ClassPreferences.getPreferences(
this.context.applicationId,
this.props.className
urlClassName
);

if (preferences.filters) {
// Normalize current filters for comparison (remove class property if it matches current className)
const currentFilters = this.props.filters.toJS().map(filter => {
// Parse the URL filters parameter to get the actual filter data
let urlFilters;
try {
urlFilters = JSON.parse(filtersParam);
} catch {
return false;
}

// Normalize URL filters for comparison (remove class property if it matches current className)
const normalizedUrlFilters = urlFilters.map(filter => {
const normalizedFilter = { ...filter };
if (normalizedFilter.class === this.props.className) {
if (normalizedFilter.class === urlClassName) {
delete normalizedFilter.class;
}
return normalizedFilter;
});
const currentFiltersString = JSON.stringify(currentFilters);
const urlFiltersString = JSON.stringify(normalizedUrlFilters);

const matchingFilter = preferences.filters.find(savedFilter => {
try {
const savedFilters = JSON.parse(savedFilter.filter);
// Normalize saved filters for comparison (remove class property if it matches current className)
const normalizedSavedFilters = savedFilters.map(filter => {
const normalizedFilter = { ...filter };
if (normalizedFilter.class === this.props.className) {
if (normalizedFilter.class === urlClassName) {
delete normalizedFilter.class;
}
return normalizedFilter;
});
const savedFiltersString = JSON.stringify(normalizedSavedFilters);
return savedFiltersString === currentFiltersString;
return savedFiltersString === urlFiltersString;
} catch {
return false;
}
Expand All @@ -117,16 +167,20 @@ export default class BrowserFilter extends React.Component {
}

return false;
} getCurrentFilterInfo() {
}

getCurrentFilterInfo() {
// Extract filterId from URL if present
const urlParams = new URLSearchParams(window.location.search);
const filterId = urlParams.get('filterId');
const filtersParam = urlParams.get('filters');

const urlClassName = this.getClassNameFromURL();

if (filterId) {
const preferences = ClassPreferences.getPreferences(
this.context.applicationId,
this.props.className
urlClassName
);

if (preferences.filters) {
Expand Down Expand Up @@ -156,36 +210,51 @@ export default class BrowserFilter extends React.Component {
}

// Check for legacy filters (filters parameter without filterId)
if (filtersParam && this.props.filters.size > 0) {
if (filtersParam) {
const preferences = ClassPreferences.getPreferences(
this.context.applicationId,
this.props.className
urlClassName
);

if (preferences.filters) {
// Normalize current filters for comparison (remove class property if it matches current className)
const currentFilters = this.props.filters.toJS().map(filter => {
// Parse the URL filters parameter to get the actual filter data
let urlFilters;
try {
urlFilters = JSON.parse(filtersParam);
} catch (error) {
console.warn('Failed to parse URL filters:', error);
return {
id: null,
name: '',
isApplied: false,
hasRelativeDates: false,
isLegacy: false
};
}

// Normalize URL filters for comparison (remove class property if it matches current className)
const normalizedUrlFilters = urlFilters.map(filter => {
const normalizedFilter = { ...filter };
if (normalizedFilter.class === this.props.className) {
if (normalizedFilter.class === urlClassName) {
delete normalizedFilter.class;
}
return normalizedFilter;
});
const currentFiltersString = JSON.stringify(currentFilters);
const urlFiltersString = JSON.stringify(normalizedUrlFilters);

const matchingFilter = preferences.filters.find(savedFilter => {
try {
const savedFilters = JSON.parse(savedFilter.filter);
// Normalize saved filters for comparison (remove class property if it matches current className)
const normalizedSavedFilters = savedFilters.map(filter => {
const normalizedFilter = { ...filter };
if (normalizedFilter.class === this.props.className) {
if (normalizedFilter.class === urlClassName) {
delete normalizedFilter.class;
}
return normalizedFilter;
});
const savedFiltersString = JSON.stringify(normalizedSavedFilters);
return savedFiltersString === currentFiltersString;
return savedFiltersString === urlFiltersString;
} catch {
return false;
}
Expand Down Expand Up @@ -224,6 +293,52 @@ export default class BrowserFilter extends React.Component {
};
}

loadFiltersFromURL() {
const urlParams = new URLSearchParams(window.location.search);
const filtersParam = urlParams.get('filters');
const filterId = urlParams.get('filterId');

const urlClassName = this.getClassNameFromURL();

// If we have a filterId, load from saved filters
if (filterId) {
const preferences = ClassPreferences.getPreferences(
this.context.applicationId,
urlClassName
);

if (preferences.filters) {
const savedFilter = preferences.filters.find(filter => filter.id === filterId);
if (savedFilter) {
try {
const filterData = JSON.parse(savedFilter.filter);
return new List(filterData.map(filter => {
const processedFilter = { ...filter, class: filter.class || urlClassName };
return new ImmutableMap(processedFilter);
}));
} catch (error) {
console.warn('Failed to parse saved filter:', error);
}
}
}
}

// If we have filters in URL but no filterId, parse them directly
if (filtersParam) {
try {
const queryFilters = JSON.parse(filtersParam);
return new List(queryFilters.map(filter => {
const processedFilter = { ...filter, class: filter.class || urlClassName };
return new ImmutableMap(processedFilter);
}));
} catch (error) {
console.warn('Failed to parse URL filters:', error);
}
}

return new List();
}

toggleMore() {
const currentFilter = this.getCurrentFilterInfo();

Expand Down Expand Up @@ -261,9 +376,11 @@ export default class BrowserFilter extends React.Component {
}

isFilterNameExists(name) {
const urlClassName = this.getClassNameFromURL();

const preferences = ClassPreferences.getPreferences(
this.context.applicationId,
this.props.className
urlClassName
);

if (preferences.filters && name) {
Expand Down Expand Up @@ -496,6 +613,17 @@ export default class BrowserFilter extends React.Component {
// Convert only Parse Date objects to JavaScript Date objects, preserve RelativeDate objects
filters = this.convertDatesForDisplay(filters);
}

// If closing the dialog and we're in edit filter mode, remove the editFilter parameter
const urlParams = new URLSearchParams(window.location.search);
const isEditFilterMode = urlParams.get('editFilter') === 'true';

if (this.state.open && isEditFilterMode) {
urlParams.delete('editFilter');
const newUrl = `${window.location.pathname}${urlParams.toString() ? '?' + urlParams.toString() : ''}`;
window.history.replaceState({}, '', newUrl);
}

this.setState(prevState => ({
open: !prevState.open,
filters: filters,
Expand Down
6 changes: 6 additions & 0 deletions src/components/BrowserFilter/FilterRow.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,12 @@ const FilterRow = ({
}) => {
const setFocus = useCallback(input => {
if (input !== null && editMode) {
// For DateTimeEntry components, don't auto-focus as it opens the calendar
// Check if the input has a focus method that opens a popover/calendar
if (input.focus && input.open) {
// This is likely a DateTimeEntry component, skip auto-focus
return;
}
input.focus();
}
}, []);
Expand Down
23 changes: 20 additions & 3 deletions src/components/CategoryList/CategoryList.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,9 +140,13 @@ export default class CategoryList extends React.Component {
return (
<div key={id}>
<div className={styles.link}>
<Link title={c.name} to={{ pathname: link }} className={className} key={id} onClick={() => this.props.classClicked()}>
<span>{count}</span>
<span>{c.name}</span>
<Link
title={c.name}
to={{ pathname: link }}
className={className}
onClick={() => this.props.classClicked()}
>
{c.name}
</Link>
{c.onEdit && (
<a
Expand All @@ -155,6 +159,7 @@ export default class CategoryList extends React.Component {
<Icon name="edit-solid" width={14} height={14} />
</a>
)}
<span className={styles.count}>{count}</span>
{(c.filters || []).length !== 0 && (
<a
className={styles.expand}
Expand Down Expand Up @@ -185,6 +190,17 @@ export default class CategoryList extends React.Component {
>
<span>{name}</span>
</Link>
{this.props.onEditFilter && (
<a
className={styles.editFilter}
onClick={e => {
e.preventDefault();
this.props.onEditFilter(c.name, filterData);
}}
>
<Icon name="edit-solid" width={14} height={14} />
</a>
)}
</div>
);
})}
Expand All @@ -202,4 +218,5 @@ CategoryList.propTypes = {
),
current: PropTypes.string.describe('Id of current category to be highlighted.'),
linkPrefix: PropTypes.string.describe('Link prefix used to generate link path.'),
onEditFilter: PropTypes.func.describe('Callback function for editing a filter.'),
};
Loading
Loading