Gantt Row Splitting Issue on Type Filter — Urgent Help Needed

Hi @Natalia_Shilova, @Listopad, @Mari, @Dzmitry,
Gantt Row Splitting Issue on Type Filter — Urgent Help Needed

I’m facing an issue where a single Gantt row is getting split into multiple entries after applying a filter on the “type” field using the dropdown.

You can reproduce the issue in this snippet:
:point_right: Code Snippet

Steps to reproduce:

  1. Open the snippet.
  2. Use the dropdown to filter by Type (e.g., select “Project”).
  3. Observe the PMDA row — it gets split into two rows, with child nodes appearing unexpectedly.

This seems to break the tree structure during filtering.

I have tried a few solutions, but nothing has worked so far. We need to fix this as a priority. Any guidance or workaround would be greatly appreciated!

Thanks in advance!

@Listopad @Mari requesting you to please help here, we need to fix this ASAP.

@Natalia_Shilova @Listopad can anyone please respond on this issue, we need to fix this on priority?

Hello webix_B_123,

We understand the urgency of this problem.

Our team is currently working on the issue to determine the cause of the unexpected behavior when filtering. It will take some additional time to investigate and implement a proper fix.

We will keep you updated on our progress and let you know as soon as we have a solution.
We appreciate your understanding.

1 Like

Thanks a lot for the reply @Natalia_Shilova , I will be waiting.

To fix the issue you need to filter tasks by both types "split" and "task" (row 53 in the example):

tasks.filter((obj) => {
       if (value === "task" && obj.type === "split") return true;
       return obj.type === value;
 });

Please check the example: Code Snippet

A bit cleaner example is to customize the gantt.services.Local and create filter logic there (the row 31 in the example):

/**
     * Filters tasks
     * @param {function} filter
     */
    filter(filter){
        const tasks = this._tasks;
        if (filter) {
            tasks.filter(filter);
            if (this.app.config.links) {
                this._links.filter(l => {
                    const source = tasks.getItem(l.source);
                    const target = tasks.getItem(l.target);
                    return source && target
                        && tasks.data.order.includes(source.id) && tasks.data.order.includes(target.id);
                });
            }
        } else {
            tasks.filter();
            if (this.app.config.links) this._links.filter();
        }
    }

And call this custom filter in the onChange handler:

onChange(){
  const value = this.getValue();
  local.filter(value ? (obj) => {
    if (value === "task" && obj.type === "split"){
        return true;
    }
    return obj.type === value;
  } : "");
}

Please check the example: Code Snippet

1 Like

Thanks for quick reply and for the resolution will check this.

tasks.filter((obj) => {
if (value === “task” && obj.type === “split”) return true;
return obj.type === value;
});

If I filer by task and split, I can not see the links, you can check in your both sinppets.

https://snippet.webix.com/npvm7c23

Hello webix_B_123,

Please check this fixed example: Code Snippet

Hello webix_B_123,

I apologize for sending you the incorrect example above.

This is the correct one: Code Snippet

This should help you see the links when filtering by both task and split.

1 Like

@Natalia_Shilova This fix is not working in my case, I’m trying replicate in snippet.

@Natalia_Shilova when I use local service overrides, initial render it self I’m not seeing links, why?

Can you give a snippet with local data and filter records based some proerty like expiryStatus values will be ‘expiry’ , nearExpiry, expired, active and etc…

Looks your solution has some issue, also can you mention some comments about the fix you have given

Hello @webix_B_123 ,

Here is an example based on the @Natalia_Shilova solution with local data and filtering by tasks status: Code Snippet .
Could you, please, provide a similar snippet with your overrides where links are missing initially?
Or, if you see any issues in the provided example, could you describe them and your expectations during filtering?

In the example we define a new filter() method in the local service that filters the inner TreeCollection of the tasks. Then, when inner store is updated, the links are refreshed and _isTaskVisible() method is called during refreshing to check if a certain task is visible ( we check source and target tasks related to the link ) . By default the method mainly checks if all task parents are open so we add an additional inOrder check to hide the invalid links related to the non-visible tasks after filtering .

@Mari @Natalia_Shilova The snippet to replicate the issue is available here:
https://snippet.webix.com/8cgstc9r

In this snippet, the links are not present by default.

Hello webix_B_123,

To fix this issue we need to add this rows of code into the default _isTaskVisible method (rows 49, 50 in the example below):

const splitParent = x.type == "split";
if (splitParent) inOrder = tasks.data.order.includes(x.id);

By default in the source code the order array includes all the tasks except children tasks of split tasks. So to understand if a child of a split task is in the order array or not, it is sufficient to check if the split task itself is in the order. We do this using the code above.

Please check the example: Code Snippet

Thanks for the quick reply, I will try this solution and let you know.

This solution works fine, Thank You @Natalia_Shilova :slightly_smiling_face:

@Mari @Natalia_Shilova
I’ve identified a couple of critical production issues that need your support urgently:

– Filtering with Child Node Issue

  • When filtering for a record that matches a child node, the task is incorrectly split into two parts.
  • This is the same issue we discussed earlier.
  • Screenshot for reference: (please see below)

Code Snippet [to replicate filter active, you can see the task get’s split]- Code Snippet

In your example to filter split tasks correctly you need the child tasks of the parent task to have the same expiryStatus (expired). Instead your parent split task has expiryStatus: "expired" but its child tasks have expiryStatus: "active" (in html code: rows 50 - parent task, row 73, 102 - child tasks).
Also to filter tasks correctly you need your items in statuses array (row 2 in html code) to be the same as options ids in richselect, expired string and id: "expiry" (row 77) are not equal.

Please check the working example: Code Snippet

@Natalia_Shilova Sorry, in the snippet I made a mistake. However, I’m still not able to replicate the same issue in the snippet with the same data. I’m not sure what the problem is. Below is the filter function I’m using:

But this solution fixed the issue.

tasks.define({
        filterMode: {
          showSubItems: true,
          openParents: false, //solution
        },
      });
import { $$ } from '@xbs/webix-pro';
import { isEmpty } from '@root/utils/utils';
import { doesTextContainQuery } from '@utils/stringSearch';

function applyGanttFilter(params) {
  const {
    filterKey,
    filterValues,
    searchFilter,
    searchFilterKey = 'text',
    filterType = 'single',
  } = params;
  const gantt = $$('mdr-standards-timelines-webix-gantt');

  if (!gantt) {
    return;
  }

  const local = gantt.getService('local');

  local.filter(
    isEmpty(filterValues) && isEmpty(searchFilter.searchText)
      ? ''
      : (task) => {
          if (task.type === 'split') {
            return false;
          }

          let hasSearchMatch = true;

          if (!isEmpty(searchFilter.searchText)) {
            const searchText = searchFilter.searchText;
            const matchCase = searchFilter.matchCase;
            const matchWholeWord = searchFilter.matchWholeWord;

            const searchValue = String(task[searchFilterKey]);

            hasSearchMatch = doesTextContainQuery(
              searchValue,
              searchText,
              matchCase,
              matchWholeWord
            );

            if (isEmpty(filterValues)) {
              return hasSearchMatch;
            }
          }

          const value = task[filterKey];
          let expiryStatusFilter;

          // Helper function to check if task should be included