Feature/metadata filtering (#418)
* added ability to filter metadata by is and contains w/ burnettk * added the empty options for metadata filtering * remove the filter when removing the corresponding column on the frontend --------- Co-authored-by: jasquat <jasquat@users.noreply.github.com> Co-authored-by: burnettk <burnettk@users.noreply.github.com>
This commit is contained in:
parent
7ccb0aec62
commit
8427f9d2fb
|
@ -549,7 +549,22 @@ class ProcessInstanceReportService:
|
||||||
]
|
]
|
||||||
if filter_for_column:
|
if filter_for_column:
|
||||||
isouter = False
|
isouter = False
|
||||||
conditions.append(instance_metadata_alias.value == filter_for_column["field_value"])
|
if "operator" not in filter_for_column or filter_for_column["operator"] == "equals":
|
||||||
|
conditions.append(instance_metadata_alias.value == filter_for_column["field_value"])
|
||||||
|
elif filter_for_column["operator"] == "not equals":
|
||||||
|
conditions.append(instance_metadata_alias.value != filter_for_column["field_value"])
|
||||||
|
elif filter_for_column["operator"] == "contains":
|
||||||
|
conditions.append(instance_metadata_alias.value.like(f"%{filter_for_column['field_value']}%"))
|
||||||
|
elif filter_for_column["operator"] == "is_empty":
|
||||||
|
# we still need to return results if the metadata value is null so make sure it's outer join
|
||||||
|
isouter = True
|
||||||
|
conditions.append(
|
||||||
|
or_(instance_metadata_alias.value.is_(None), instance_metadata_alias.value == "")
|
||||||
|
)
|
||||||
|
elif filter_for_column["operator"] == "is_not_empty":
|
||||||
|
conditions.append(
|
||||||
|
or_(instance_metadata_alias.value.is_not(None), instance_metadata_alias.value != "")
|
||||||
|
)
|
||||||
process_instance_query = process_instance_query.join(
|
process_instance_query = process_instance_query.join(
|
||||||
instance_metadata_alias, and_(*conditions), isouter=isouter
|
instance_metadata_alias, and_(*conditions), isouter=isouter
|
||||||
).add_columns(func.max(instance_metadata_alias.value).label(column["accessor"]))
|
).add_columns(func.max(instance_metadata_alias.value).label(column["accessor"]))
|
||||||
|
|
|
@ -41,6 +41,7 @@ import {
|
||||||
convertSecondsToFormattedDateString,
|
convertSecondsToFormattedDateString,
|
||||||
convertSecondsToFormattedDateTime,
|
convertSecondsToFormattedDateTime,
|
||||||
convertSecondsToFormattedTimeHoursMinutes,
|
convertSecondsToFormattedTimeHoursMinutes,
|
||||||
|
getKeyByValue,
|
||||||
getPageInfoFromSearchParams,
|
getPageInfoFromSearchParams,
|
||||||
modifyProcessIdentifierForPathParam,
|
modifyProcessIdentifierForPathParam,
|
||||||
refreshAtInterval,
|
refreshAtInterval,
|
||||||
|
@ -67,6 +68,7 @@ import {
|
||||||
User,
|
User,
|
||||||
ErrorForDisplay,
|
ErrorForDisplay,
|
||||||
PermissionsToCheck,
|
PermissionsToCheck,
|
||||||
|
FilterOperatorMapping,
|
||||||
} from '../interfaces';
|
} from '../interfaces';
|
||||||
import ProcessModelSearch from './ProcessModelSearch';
|
import ProcessModelSearch from './ProcessModelSearch';
|
||||||
import ProcessInstanceReportSearch from './ProcessInstanceReportSearch';
|
import ProcessInstanceReportSearch from './ProcessInstanceReportSearch';
|
||||||
|
@ -164,6 +166,14 @@ export default function ProcessInstanceListTable({
|
||||||
const preferredUsername = UserService.getPreferredUsername();
|
const preferredUsername = UserService.getPreferredUsername();
|
||||||
const userEmail = UserService.getUserEmail();
|
const userEmail = UserService.getUserEmail();
|
||||||
|
|
||||||
|
const filterOperatorMappings: FilterOperatorMapping = {
|
||||||
|
Is: { id: 'equals', requires_value: true },
|
||||||
|
'Is Not': { id: 'not equals', requires_value: true },
|
||||||
|
Contains: { id: 'contains', requires_value: true },
|
||||||
|
'Is Empty': { id: 'is_empty', requires_value: false },
|
||||||
|
'Is Not Empty': { id: 'is_not_empty', requires_value: false },
|
||||||
|
};
|
||||||
|
|
||||||
const processInstanceListPathPrefix =
|
const processInstanceListPathPrefix =
|
||||||
variant === 'all'
|
variant === 'all'
|
||||||
? '/admin/process-instances/all'
|
? '/admin/process-instances/all'
|
||||||
|
@ -361,6 +371,27 @@ export default function ProcessInstanceListTable({
|
||||||
setProcessInstanceReportSelection(processInstanceReport);
|
setProcessInstanceReportSelection(processInstanceReport);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (additionalReportFilters) {
|
||||||
|
additionalReportFilters.forEach((arf: ReportFilter) => {
|
||||||
|
if (!reportMetadataBodyToUse.filter_by.includes(arf)) {
|
||||||
|
reportMetadataBodyToUse.filter_by.push(arf);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the showActionColumn is set to true, we need to include the with_oldest_open_task in the query params
|
||||||
|
if (
|
||||||
|
showActionsColumn &&
|
||||||
|
!reportMetadataBodyToUse.filter_by.some(
|
||||||
|
(rf: ReportFilter) => rf.field_name === 'with_oldest_open_task'
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
const withOldestReportFilter = {
|
||||||
|
field_name: 'with_oldest_open_task',
|
||||||
|
field_value: true,
|
||||||
|
};
|
||||||
|
reportMetadataBodyToUse.filter_by.push(withOldestReportFilter);
|
||||||
|
}
|
||||||
|
|
||||||
// a bit hacky, clear out all filters before setting them from report metadata
|
// a bit hacky, clear out all filters before setting them from report metadata
|
||||||
// to ensure old filters are cleared out.
|
// to ensure old filters are cleared out.
|
||||||
|
@ -417,6 +448,13 @@ export default function ProcessInstanceListTable({
|
||||||
setShowFilterOptions(true);
|
setShowFilterOptions(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (filtersEnabled) {
|
||||||
|
HttpService.makeCallToBackend({
|
||||||
|
path: `/user-groups/for-current-user`,
|
||||||
|
successCallback: setUserGroups,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line prefer-const
|
// eslint-disable-next-line prefer-const
|
||||||
let { page, perPage } = getPageInfoFromSearchParams(
|
let { page, perPage } = getPageInfoFromSearchParams(
|
||||||
searchParams,
|
searchParams,
|
||||||
|
@ -428,30 +466,8 @@ export default function ProcessInstanceListTable({
|
||||||
// eslint-disable-next-line prefer-destructuring
|
// eslint-disable-next-line prefer-destructuring
|
||||||
perPage = perPageOptions[1];
|
perPage = perPageOptions[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
const queryParamString = `per_page=${perPage}&page=${page}`;
|
const queryParamString = `per_page=${perPage}&page=${page}`;
|
||||||
if (additionalReportFilters) {
|
|
||||||
additionalReportFilters.forEach((arf: ReportFilter) => {
|
|
||||||
if (!reportMetadataBodyToUse.filter_by.includes(arf)) {
|
|
||||||
reportMetadataBodyToUse.filter_by.push(arf);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the showActionColumn is set to true, we need to include the with_oldest_open_task in the query params
|
|
||||||
if (showActionsColumn) {
|
|
||||||
reportMetadataBodyToUse.filter_by.push({
|
|
||||||
field_name: 'with_oldest_open_task',
|
|
||||||
field_value: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filtersEnabled) {
|
|
||||||
HttpService.makeCallToBackend({
|
|
||||||
path: `/user-groups/for-current-user`,
|
|
||||||
successCallback: setUserGroups,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
HttpService.makeCallToBackend({
|
HttpService.makeCallToBackend({
|
||||||
path: `${processInstanceApiSearchPath}?${queryParamString}`,
|
path: `${processInstanceApiSearchPath}?${queryParamString}`,
|
||||||
successCallback: setProcessInstancesFromResult,
|
successCallback: setProcessInstancesFromResult,
|
||||||
|
@ -952,6 +968,13 @@ export default function ProcessInstanceListTable({
|
||||||
(rc: ReportColumn) => rc.accessor !== reportColumn.accessor
|
(rc: ReportColumn) => rc.accessor !== reportColumn.accessor
|
||||||
);
|
);
|
||||||
Object.assign(reportMetadataCopy, { columns: newColumns });
|
Object.assign(reportMetadataCopy, { columns: newColumns });
|
||||||
|
const newFilters = reportMetadataCopy.filter_by.filter(
|
||||||
|
(rf: ReportFilter) => rf.field_name !== reportColumn.accessor
|
||||||
|
);
|
||||||
|
Object.assign(reportMetadataCopy, {
|
||||||
|
columns: newColumns,
|
||||||
|
filter_by: newFilters,
|
||||||
|
});
|
||||||
setReportMetadata(reportMetadataCopy);
|
setReportMetadata(reportMetadataCopy);
|
||||||
setRequiresRefilter(true);
|
setRequiresRefilter(true);
|
||||||
}
|
}
|
||||||
|
@ -963,6 +986,18 @@ export default function ProcessInstanceListTable({
|
||||||
setReportColumnToOperateOn(null);
|
setReportColumnToOperateOn(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getFilterOperatorFromReportColumn = (
|
||||||
|
reportColumnForEditing: ReportColumnForEditing
|
||||||
|
) => {
|
||||||
|
if (reportColumnForEditing.filter_operator) {
|
||||||
|
// eslint-disable-next-line prefer-destructuring
|
||||||
|
return Object.entries(filterOperatorMappings).filter(([_key, value]) => {
|
||||||
|
return value.id === reportColumnForEditing.filter_operator;
|
||||||
|
})[0][1];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
const getNewFiltersFromReportForEditing = (
|
const getNewFiltersFromReportForEditing = (
|
||||||
reportColumnForEditing: ReportColumnForEditing
|
reportColumnForEditing: ReportColumnForEditing
|
||||||
) => {
|
) => {
|
||||||
|
@ -980,14 +1015,23 @@ export default function ProcessInstanceListTable({
|
||||||
const existingReportFilter = getFilterByFromReportMetadata(
|
const existingReportFilter = getFilterByFromReportMetadata(
|
||||||
reportColumnForEditing.accessor
|
reportColumnForEditing.accessor
|
||||||
);
|
);
|
||||||
|
const filterOperator = getFilterOperatorFromReportColumn(
|
||||||
|
reportColumnForEditing
|
||||||
|
);
|
||||||
if (existingReportFilter) {
|
if (existingReportFilter) {
|
||||||
const existingReportFilterIndex =
|
const existingReportFilterIndex =
|
||||||
reportMetadataCopy.filter_by.indexOf(existingReportFilter);
|
reportMetadataCopy.filter_by.indexOf(existingReportFilter);
|
||||||
if (reportColumnForEditing.filter_field_value) {
|
if (filterOperator && !filterOperator.requires_value) {
|
||||||
|
newReportFilter.field_value = '';
|
||||||
|
newReportFilters[existingReportFilterIndex] = newReportFilter;
|
||||||
|
} else if (reportColumnForEditing.filter_field_value) {
|
||||||
newReportFilters[existingReportFilterIndex] = newReportFilter;
|
newReportFilters[existingReportFilterIndex] = newReportFilter;
|
||||||
} else {
|
} else {
|
||||||
newReportFilters.splice(existingReportFilterIndex, 1);
|
newReportFilters.splice(existingReportFilterIndex, 1);
|
||||||
}
|
}
|
||||||
|
} else if (filterOperator && !filterOperator.requires_value) {
|
||||||
|
newReportFilter.field_value = '';
|
||||||
|
newReportFilters = newReportFilters.concat([newReportFilter]);
|
||||||
} else if (reportColumnForEditing.filter_field_value) {
|
} else if (reportColumnForEditing.filter_field_value) {
|
||||||
newReportFilters = newReportFilters.concat([newReportFilter]);
|
newReportFilters = newReportFilters.concat([newReportFilter]);
|
||||||
}
|
}
|
||||||
|
@ -1073,6 +1117,19 @@ export default function ProcessInstanceListTable({
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const setReportColumnConditionOperator = (selectedItem: string) => {
|
||||||
|
if (reportColumnToOperateOn) {
|
||||||
|
const reportColumnToOperateOnCopy = {
|
||||||
|
...reportColumnToOperateOn,
|
||||||
|
};
|
||||||
|
const filterOperator = filterOperatorMappings[selectedItem];
|
||||||
|
reportColumnToOperateOnCopy.filter_operator = filterOperator.id;
|
||||||
|
setReportColumnToOperateOn(reportColumnToOperateOnCopy);
|
||||||
|
setRequiresRefilter(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||||
const reportColumnForm = () => {
|
const reportColumnForm = () => {
|
||||||
if (reportColumnFormMode === '') {
|
if (reportColumnFormMode === '') {
|
||||||
return null;
|
return null;
|
||||||
|
@ -1120,18 +1177,40 @@ export default function ProcessInstanceListTable({
|
||||||
]);
|
]);
|
||||||
if (reportColumnToOperateOn && reportColumnToOperateOn.filterable) {
|
if (reportColumnToOperateOn && reportColumnToOperateOn.filterable) {
|
||||||
formElements.push(
|
formElements.push(
|
||||||
<TextInput
|
<Dropdown
|
||||||
id="report-column-condition-value"
|
titleText="Operator"
|
||||||
name="report-column-condition-value"
|
id="report-column-condition-operator"
|
||||||
labelText="Condition Value"
|
items={Object.keys(filterOperatorMappings)}
|
||||||
value={
|
selectedItem={getKeyByValue(
|
||||||
reportColumnToOperateOn
|
filterOperatorMappings,
|
||||||
? reportColumnToOperateOn.filter_field_value
|
reportColumnToOperateOn.filter_operator,
|
||||||
: ''
|
'id'
|
||||||
}
|
)}
|
||||||
onChange={setReportColumnConditionValue}
|
onChange={(value: any) => {
|
||||||
|
setReportColumnConditionOperator(value.selectedItem);
|
||||||
|
setRequiresRefilter(true);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const filterOperator = getFilterOperatorFromReportColumn(
|
||||||
|
reportColumnToOperateOn
|
||||||
|
);
|
||||||
|
if (filterOperator && filterOperator.requires_value) {
|
||||||
|
formElements.push(
|
||||||
|
<TextInput
|
||||||
|
id="report-column-condition-value"
|
||||||
|
name="report-column-condition-value"
|
||||||
|
labelText="Condition Value"
|
||||||
|
value={
|
||||||
|
reportColumnToOperateOn
|
||||||
|
? reportColumnToOperateOn.filter_field_value
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
onChange={setReportColumnConditionValue}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
formElements.push(
|
formElements.push(
|
||||||
<div className="vertical-spacer-to-allow-combo-box-to-expand-in-modal" />
|
<div className="vertical-spacer-to-allow-combo-box-to-expand-in-modal" />
|
||||||
|
@ -1273,6 +1352,7 @@ export default function ProcessInstanceListTable({
|
||||||
labelText="Include oldest open task information"
|
labelText="Include oldest open task information"
|
||||||
id="with-oldest-open-task-checkbox"
|
id="with-oldest-open-task-checkbox"
|
||||||
checked={withOldestOpenTask}
|
checked={withOldestOpenTask}
|
||||||
|
disabled={showActionsColumn}
|
||||||
onChange={(value: any) => {
|
onChange={(value: any) => {
|
||||||
setWithOldestOpenTask(value.target.checked);
|
setWithOldestOpenTask(value.target.checked);
|
||||||
setRequiresRefilter(true);
|
setRequiresRefilter(true);
|
||||||
|
|
|
@ -37,6 +37,19 @@ export const underscorizeString = (inputString: string) => {
|
||||||
return slugifyString(inputString).replace(/-/g, '_');
|
return slugifyString(inputString).replace(/-/g, '_');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getKeyByValue = (
|
||||||
|
object: any,
|
||||||
|
value: string,
|
||||||
|
onAttribute?: string
|
||||||
|
) => {
|
||||||
|
return Object.keys(object).find((key) => {
|
||||||
|
if (onAttribute) {
|
||||||
|
return object[key][onAttribute] === value;
|
||||||
|
}
|
||||||
|
return object[key] === value;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export const recursivelyChangeNullAndUndefined = (obj: any, newValue: any) => {
|
export const recursivelyChangeNullAndUndefined = (obj: any, newValue: any) => {
|
||||||
if (obj === null || obj === undefined) {
|
if (obj === null || obj === undefined) {
|
||||||
return newValue;
|
return newValue;
|
||||||
|
|
|
@ -128,6 +128,15 @@ export interface ProcessReference {
|
||||||
|
|
||||||
export type ObjectWithStringKeysAndValues = { [key: string]: string };
|
export type ObjectWithStringKeysAndValues = { [key: string]: string };
|
||||||
|
|
||||||
|
export interface FilterOperator {
|
||||||
|
id: string;
|
||||||
|
requires_value: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FilterOperatorMapping {
|
||||||
|
[key: string]: FilterOperator;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ProcessFile {
|
export interface ProcessFile {
|
||||||
content_type: string;
|
content_type: string;
|
||||||
last_modified: string;
|
last_modified: string;
|
||||||
|
|
Loading…
Reference in New Issue