Preset filters for customized data

Hello,

We have an enumeration field from the document model representing the process status of our workflow. This field is used and displayed in some places including the overview.
But in one particular case, for one particular value, we overwrite the display in the overview.

So there can be multiple “Contract draft” statuses but some of them can be overwritten to “Not yet assigned”.

In the preset filters, we don’t have “Not yet assigned” as it doesn’t exist in the enumeration field. But this is a requirement.

We’d like to add this filter in the preset and when selected, it looks for all ContractDraft documents without any Task Assignee.

But we don’t want to change the whole workflow of our project just for an overwritten value (we don’t use it anywhere else).

So I created a middleware to list myself the documents with custom filters.

import { Middleware } from "redux";

import { Activity, ActivityActions, ActivitySelectors } from "@com.mgmtp.a12.client/client-core/lib/core/activity";
import { OverviewEngineActions } from "@com.mgmtp.a12.client/client-core/lib/core/view";
import { LoggerFactory } from "@com.mgmtp.a12.utils/utils-logging";
import { StoreFactories } from "@com.mgmtp.a12.client/client-core/lib/core/store";
import { OverviewEngineApi } from "@com.mgmtp.a12.overviewengine/overviewengine-core/lib/main/view/api";
import { Model, ModelSelectors } from "@com.mgmtp.a12.client/client-core/lib/core/model";

import { CandidateConstants } from "../../constants";
import { CandidateModelPath } from "../../path";
import { TASK_OVERVIEW_VIEW_KEY } from "../../../../common/constants/workflowsConstants";
import { listCandidateDocuments } from "../../../../rest/candidate/candidate";
import { LocalFieldBasedFiltering } from "../../../../utils";

import { ProcessStatus } from "../types";

import FilterMap = OverviewEngineApi.FilterMap;
import SetDataPayload = ActivityActions.SetDataPayload;

const logger = LoggerFactory.getLogger("[middlewares/candidate/queryParametersChangedMiddleware]");

export const queryParametersChangedMiddleware: Middleware = StoreFactories.createMiddleware((api, next, action) => {
	if (OverviewEngineActions.queryParametersChanged.match(action)) {
		const activityId = action.payload.activityId;
		const activity = ActivitySelectors.activityById(activityId)(api.getState());

		const { fieldBasedFilters } = action.payload;

		if (!activity || !isCandidateOverviewActivity(activity) || !fieldBasedFilters) {
			return next(action);
		}

		if (!isNotYetAssignedStatusSelected(fieldBasedFilters)) {
			return next(action);
		}


		const documentModel = ModelSelectors.modelByName(CandidateConstants.CANDIDATE_DM, Model.isDocumentModel)(api.getState())

		if (!documentModel) {
			return next(action);
		}

		const statusFilters: OverviewEngineApi.FilterMap = getOrUpdateStatusFilterMap(fieldBasedFilters);

		const newFieldBasedFilters: FilterMap = {
			...fieldBasedFilters,
			...statusFilters
		}

		const filters = LocalFieldBasedFiltering.toStringArray(newFieldBasedFilters, documentModel);

		// As NotYetAssigned is selected, we'll select the filter to check that task assignee doesn't exist or is null
		filters.push("-" + CandidateModelPath.Candidate.MetaData.Process.Assignees.TaskAssigneeName.toJsonPath() + ":");

		listCandidateDocuments(filters).then(docs => {
			const payload: SetDataPayload = {
				activityId: activityId,
				data: {
					documents: docs,
					totalDocumentsCount: docs.length
				}
			}
			return next(ActivityActions.setData(payload))
		}).catch(
			error => {
				logger.error("Error while loading documents", error);
			}
		);
	}
	return next(action);
});

const isCandidateOverviewActivity = (activity: Activity): boolean => {
	const { descriptor } = activity;
	return (
		descriptor.module === CandidateConstants.CANDIDATE_MODULE &&
		descriptor.view === TASK_OVERVIEW_VIEW_KEY
	);
};

const isNotYetAssignedStatusSelected = (fieldBasedFilters: OverviewEngineApi.FilterMap): boolean => {
	const statusFilterOptions = fieldBasedFilters[CandidateModelPath.Candidate.MetaData.Process.Status.toJsonPath()];
	if (statusFilterOptions?.filterType !== "Enumeration") {
		return false;
	}
	const criteria = statusFilterOptions.criteria as { readonly selectedValues: string[] };
	return criteria.selectedValues.includes(ProcessStatus.NOT_YET_ASSIGNED);
};

const getOrUpdateStatusFilterMap = (fieldBasedFilters: OverviewEngineApi.FilterMap): OverviewEngineApi.FilterMap => {
	const statusFilterOptions = fieldBasedFilters[
		CandidateModelPath.Candidate.MetaData.Process.Status.toJsonPath()
		] as OverviewEngineApi.Filter.EnumerationOptions;

	const newSelectedValues = statusFilterOptions.criteria
		? [...statusFilterOptions.criteria.selectedValues, ProcessStatus.CONTRACT_DRAFT]
		: [ProcessStatus.CONTRACT_DRAFT];

	const index = newSelectedValues.indexOf(ProcessStatus.NOT_YET_ASSIGNED);
	if (index !== -1) {
		newSelectedValues.splice(index, 1);
	}

	const newOptions: OverviewEngineApi.Filter.EnumerationOptions = {
		filterType: statusFilterOptions.filterType,
		criteria: {
			selectedValues: newSelectedValues
		}
	};

	return {
		[CandidateModelPath.Candidate.MetaData.Process.Status.toJsonPath()]: newOptions
	};
};

The documents are well returned by the rpc, but just after there is a TaskOverviewDataProvider operation that list documents with the preset filters, i.e in our case, the “Not yet assigned” which is never persisted.

[
    {
        "jsonrpc": "2.0",
        "id": "TaskOverviewDataProvider-4",
        "method": "LIST_DOCUMENTS",
        "params": {
            "documentModelName": "CandidateDMProcess",
            "filter": {
                "filters": [
                    "Candidate.MetaData.Process.Status:NotYetAssigned",
                    "a12wf.workflowsInternalDocumentStatus:(EMPTY OR NON_EMPTY)"
                ],
                "fulltext": "",
                "lang": ""
            },
            "page": {
                "offset": 0,
                "limit": 10
            },
            "sort": [],
            "facets": []
        }
    }
]

How can we avoid this ?
Would you come up with a better solution ?

Thanks for reading.

Moin @alexis-raw-iron,
it sounds like your solution might conflict with the implementation of the TaskOverviewDataProvider implementation.
We will need some time for investigation and reach out to you as soon as we can provide a better solution for you. :slight_smile:

Moin @alexis-raw-iron,
for clarification:

  • You get the expected documents with status “NotYetAssigned” when you send your RPC-request in queryParametersChangedMiddleware.
  • Right after your RPC-operation has been executed, the TaskOverviewDataProvider operation from Workflows is sent as well, but now containing also the filter for “NotYetAssigned”, which shouldn’t be the case. So you expected the only filter for TaskOverviewDataProvider to be “a12wf.workflowsInternalDocumentStatus:(EMPTY OR NON_EMPTY)”, is that correct?

Where do you register the middleware queryParametersChangedMiddleware in your project?

Hello,

Yes for the first point, knowing that “NotYetAssigned” is actually the status Candidate.MetaData.Process.Status = ContractDraft and Candidate.MetaData.Process.Assignees.TaskAssigneeName = null (or "").

For the second point, I didn’t really expect anything as I was not aware it would run if I changed the flow in the middlewares by returning another action in my middleware (here the setData action, instead of the current action).
So yes, the TaskOverviewDataProvider seems to take the selected filters and actually dispacthes another setData action which makes my custom one useless. My first idea was to change the selected filters but there is no filter options to check if a value is null.

The middleware is set in the module:

const module = (): Module => ({
	id: "CandidateModule",
	model: () => candidateAppModel as ApplicationModel,
	views: () => viewComponentProvider,
	sagas: () => [...sagas],
	middlewares: () => [...middlewares],
	dataReducers: () => [...dataReducers],
	dataProviders: () => [...dataProviders]
});
export const middlewares = [
	addOverviewFilterMiddleware,
	onEventButtonMarkOfferAsSentMiddleware,
	redirectEmployeeFormAfterCreatedMiddleware, 
	queryParametersChangedMiddleware
];

Moin @alexis-raw-iron,

we had a look into your issue. Could you please check and change this one line as follows in your queryParametersChangedMiddleware:

			return next(ActivityActions.setData(payload)) 

to

			return next(action)

We assume that the middleware for the the additionally dispatched setData action overrides your changes to the filters. Please also check the redux state and verify if your “NotYetAssigned” is properly set there as you would expect it.

Hello,

I’ll have to try again, I don’t really remember the exact results but I tried that at first, this is the reason why I tried to return another action but I can tell you though that it didn’t work neither.

Plus, I unfortunately lost all my shelved changes (I completely forgot to push it on a temp branch) so I don’t have my code anymore…

Let me get back to you when I return to this subject.

Hey @alexis-raw-iron,
Have you had the time to take another look at the problem you described? Did you succeeded to solve your issue or is it still valid?

Hello,

No I still didn’t go back on this subject, we set this feature aside for now.
If you prefer you can close the topic and we will ask again if needed when working on it again.