function TestScenarioDataSelectorPanel()

in packages/scesim-editor/src/drawer/TestScenarioDrawerDataSelectorPanel.tsx [170:622]


function TestScenarioDataSelectorPanel() {
  const { i18n } = useTestScenarioEditorI18n();
  const { externalModelsByNamespace } = useExternalModels();
  const dataObjects = useTestScenarioEditorStore((state) =>
    state.computed(state).getDataObjects(externalModelsByNamespace)
  );
  const scesimModel = useTestScenarioEditorStore((state) => state.scesim.model);
  const tableStatus = useTestScenarioEditorStore((state) => state.table);
  const tabStatus = useTestScenarioEditorStore((state) => state.navigation.tab);
  const testScenarioEditorStoreApi = useTestScenarioEditorStoreApi();
  const testScenarioType = scesimModel.ScenarioSimulationModel.settings.type?.__$$text.toUpperCase();
  const referencedDmnNamespace = scesimModel.ScenarioSimulationModel.settings.dmnNamespace?.__$$text;

  const selectedColumnMetadata =
    tabStatus === TestScenarioEditorTab.SIMULATION
      ? tableStatus.simulation.selectedColumn
      : tableStatus.background.selectedColumn;

  const [allExpanded, setAllExpanded] = useState(false);
  const [dataSelectorStatus, setDataSelectorStatus] = useState(TestScenarioDataSelectorState.DISABLED);
  const [filteredItems, setFilteredItems] = useState(dataObjects);
  const [treeViewStatus, setTreeViewStatus] = useState({
    activeItems: [] as TestScenarioTreeViewDataItem[],
    searchKey: "",
    isExpanded: false,
  });

  /** It filters out all the Data Objects and their Children already assigned in the table */
  const filterOutAlreadyAssignedDataObjectsAndChildren = useCallback(
    (selectedColumnExpressionElement: SceSim__expressionElementsType | undefined, isBackground: boolean) => {
      const testScenarioDescriptor = isBackground
        ? scesimModel.ScenarioSimulationModel.background.scesimModelDescriptor
        : scesimModel.ScenarioSimulationModel.simulation.scesimModelDescriptor;
      const assignedExpressionElements = (testScenarioDescriptor.factMappings.FactMapping ?? []).reduce(
        (acc: SceSim__expressionElementsType[], factMapping) =>
          factMapping.expressionElements ? [...acc, factMapping.expressionElements] : acc,
        []
      );

      const assignedIds = (testScenarioDescriptor.factMappings.FactMapping ?? [])
        .filter((factMapping) => factMapping.expressionElements && factMapping.expressionElements.ExpressionElement)
        .reduce((assignedIds, factMapping) => {
          const assignedId = (factMapping.expressionElements?.ExpressionElement ?? [])
            .map((expressionElement) => {
              if (factMapping.expressionAlias?.__$$text === "value") {
                return `${expressionElement.step.__$$text}.value`;
              }
              return expressionElement.step.__$$text;
            })
            .join(".");

          // parent
          if (factMapping.factIdentifier.name?.__$$text) {
            assignedIds.add(factMapping.factIdentifier.name.__$$text);
          }
          assignedIds.add(assignedId);
          return assignedIds;
        }, new Set<string>());

      // An Empty column has been selected. Filtering out all assigined Instances
      if (
        !selectedColumnExpressionElement?.ExpressionElement ||
        selectedColumnExpressionElement?.ExpressionElement?.length === 0
      ) {
        return dataObjects
          .map((object) => cloneDeep(object)) // Deep copy: the Objects may mutate due to children filtering
          .filter((dataObject) => isRootDataObjectAssignable(dataObject, assignedExpressionElements));
      }

      // In case of not empty column, it keeps the selected root Fact Mapping (Instance) and then filtering out the already
      // assigned children Data Objects.
      return dataObjects
        .map((object) => cloneDeep(object)) // Deep copy: the Objects may mutate due to children filtering
        .filter((dataObject) => !isRootDataObjectAssignable(dataObject, [selectedColumnExpressionElement]))
        .reduce((acc, dataObject) => {
          filterOutDataObjectChildrenByExpressionElements(dataObject, [...assignedIds]);
          if (dataObject.children?.length === 0 && assignedIds.has(dataObject.id)) {
            return acc;
          }
          return [...acc, dataObject];
        }, []);
    },
    [dataObjects, scesimModel.ScenarioSimulationModel]
  );

  useEffect(() => {
    console.debug("========SELECTOR PANEL USE EFFECT===========");
    console.debug("Selected Column:", selectedColumnMetadata);
    console.debug("All Data Objects:", dataObjects);

    /**
     * Case 1: No columns selected OR a column of OTHER type (eg. Description column).
     * In such a case, the selector status is disabled with no filtered items and no active TreeViewItems
     */
    if (!selectedColumnMetadata || selectedColumnMetadata.factMapping.expressionIdentifier.type?.__$$text == "OTHER") {
      setDataSelectorStatus(TestScenarioDataSelectorState.DISABLED);
      setFilteredItems(dataObjects);
      setTreeViewStatus({ activeItems: [], searchKey: "", isExpanded: false });
      console.debug("Case 1");
      console.debug("=============USE EFFECT END===============");
      return;
    }

    /**
     * Case 2: A GIVEN / EXPECT column with EMPTY field (2nd level header) is selected.
     * In such a case, the selector status is enabled with no active TreeViewItems and a filtered items list which shows:
     * - All the NOT-assigned fields of the selected column instance
     * - All the NOT-assigned fields of the NOT-ASSIGNED instances, if the selected column doesn't have an instance (1th level header) assigned
     */
    if (selectedColumnMetadata.factMapping.className.__$$text === "java.lang.Void") {
      const isFactIdentifierAssigned =
        selectedColumnMetadata.factMapping.factIdentifier.className!.__$$text !== "java.lang.Void";

      let filteredDataObjects: TestScenarioDataObject[] = filterOutAlreadyAssignedDataObjectsAndChildren(
        selectedColumnMetadata.factMapping.expressionElements,
        selectedColumnMetadata.isBackground
      );

      /** Applying User search key to the filteredDataObjects, if present */
      const isUserFilterPresent = treeViewStatus.searchKey.trim() !== "";
      if (isUserFilterPresent) {
        filteredDataObjects = filteredDataObjects.filter((item) =>
          filterDataObjectsByName(item, treeViewStatus.searchKey)
        );
      }

      setDataSelectorStatus(TestScenarioDataSelectorState.ENABLED);
      setFilteredItems(filteredDataObjects);
      setTreeViewStatus((prev) => {
        return {
          ...prev,
          activeItems: [],
          isExpanded: isFactIdentifierAssigned || isUserFilterPresent,
        };
      });
      console.debug("Case 2");
      console.debug("Filtered Data Objects:", filteredDataObjects);
      console.debug("=============USE EFFECT END===============");
      return;
    }

    /**
     * Case 3 (Final): A GIVEN / EXPECT column with a defined INSTANCE and FIELD
     * In such a case, the selector enabled the TreeView only the Instance and Field of the selected columns is activated TreeViewItems and shown (filtered):
     */
    const factIdentifier = selectedColumnMetadata.factMapping.expressionElements!.ExpressionElement![0].step.__$$text;
    const filteredDataObjects = dataObjects.filter((dataObject) => filterTypesItems(dataObject, factIdentifier));
    const isExpressionType = selectedColumnMetadata.factMapping.factMappingValueType!.__$$text === "EXPRESSION";
    const isSimpleTypeFact =
      selectedColumnMetadata.factMapping.expressionElements!.ExpressionElement!.length === 1 &&
      selectedColumnMetadata.factMapping.className.__$$text !== "java.lang.Void";
    let fieldId: string;
    if (isExpressionType) {
      fieldId = selectedColumnMetadata.factMapping.expressionElements!.ExpressionElement![0].step.__$$text;
    } else if (isSimpleTypeFact) {
      fieldId = selectedColumnMetadata.factMapping
        .expressionElements!.ExpressionElement![0].step.__$$text.concat(".")
        .concat("value");
    } else {
      fieldId = selectedColumnMetadata.factMapping
        .expressionElements!.ExpressionElement!.map((expressionElement) => expressionElement.step.__$$text)
        .join(".");
    }

    const treeViewItemToActivate = findTestScenarioDataObjectById(dataObjects, fieldId)!;

    setDataSelectorStatus(TestScenarioDataSelectorState.TREEVIEW_ENABLED_ONLY);
    setFilteredItems(filteredDataObjects);
    setTreeViewStatus({ activeItems: [treeViewItemToActivate], searchKey: "", isExpanded: true });
    console.debug("Case 3");
    console.debug("=============USE EFFECT END===============");
  }, [
    dataObjects,
    filterOutAlreadyAssignedDataObjectsAndChildren,
    scesimModel,
    selectedColumnMetadata,
    treeViewStatus.searchKey,
  ]);

  const treeViewEmptyStatus = useMemo(() => {
    const isReferencedFileLoaded =
      testScenarioType === "RULE" || externalModelsByNamespace?.has(referencedDmnNamespace!);
    const isTreeViewNotEmpty = filteredItems.length > 0;
    const activeItem = treeViewStatus.activeItems[0];
    const treeViewVisibleStatus = isReferencedFileLoaded ? (isTreeViewNotEmpty ? "visible" : "hidden") : "loading";
    const title =
      dataObjects.length === 0
        ? testScenarioType === "DMN"
          ? i18n.drawer.dataSelector.emptyDataObjectsTitleDMN
          : i18n.drawer.dataSelector.emptyDataObjectsTitleRule
        : activeItem !== undefined
          ? i18n.drawer.dataSelector.emptyDataObjectsTitle
          : i18n.drawer.dataSelector.emptyDataObjectsMissingTitle;
    const description =
      dataObjects.length === 0
        ? testScenarioType === "DMN"
          ? i18n.drawer.dataSelector.emptyDataObjectsDescriptionDMN
          : i18n.drawer.dataSelector.emptyDataObjectsDescriptionRule
        : activeItem !== undefined
          ? i18n.drawer.dataSelector.emptyDataObjectsDescription
          : i18n.drawer.dataSelector.emptyDataObjectsMissingDescription;
    {
      testScenarioType === "DMN"
        ? i18n.drawer.dataSelector.emptyDataObjectsTitleDMN
        : i18n.drawer.dataSelector.emptyDataObjectsTitleRule;
    }

    return { description: description, icon: WarningTriangleIcon, title: title, visibility: treeViewVisibleStatus };
  }, [
    dataObjects.length,
    externalModelsByNamespace,
    filteredItems.length,
    i18n.drawer.dataSelector,
    referencedDmnNamespace,
    testScenarioType,
    treeViewStatus,
  ]);

  const insertDataObjectButtonStatus = useMemo(() => {
    if (!selectedColumnMetadata) {
      return {
        message: i18n.drawer.dataSelector.insertDataObjectTooltipColumnSelectionMessage,
        enabled: false,
      };
    }

    if (!treeViewStatus.activeItems === undefined || treeViewStatus.activeItems.length !== 1) {
      return {
        message: i18n.drawer.dataSelector.insertDataObjectTooltipDataObjectSelectionMessage,
        enabled: false,
      };
    }
    const activeItem = treeViewStatus.activeItems[0];
    const unassignedDataObjects = filterOutAlreadyAssignedDataObjectsAndChildren(
      selectedColumnMetadata.factMapping.expressionElements,
      selectedColumnMetadata.isBackground
    );

    const isAssignable =
      activeItem !== undefined &&
      (activeItem.children !== undefined &&
        activeItem.children.length > 0 &&
        activeItem.expressionElements.length > 1) === false &&
      findTestScenarioDataObjectById(unassignedDataObjects, activeItem.id) !== undefined;

    if (treeViewStatus.activeItems.length === 1 && isAssignable === false) {
      return {
        message: i18n.drawer.dataSelector.insertDataObjectTooltipDataObjectAlreadyAssignedMessage,
        enabled: false,
      };
    }

    return { message: i18n.drawer.dataSelector.insertDataObjectTooltipDataObjectAssignMessage, enabled: true };
  }, [filterOutAlreadyAssignedDataObjectsAndChildren, i18n, selectedColumnMetadata, treeViewStatus]);

  const onAllExpandedToggle = useCallback((_event) => {
    setAllExpanded((prev) => !prev);
  }, []);

  const onInsertDataObjectClick = useCallback(() => {
    const userSelectedTestScenarioObject = treeViewStatus.activeItems[0];

    if (userSelectedTestScenarioObject === undefined) {
      console.error("No Data Object selected in the item view.");
      return;
    }

    const rootSelectedTestScenarioDataObject = findDataObjectRootParent(
      dataObjects,
      treeViewStatus.activeItems[0].id.toString()
    );
    const isBackground = selectedColumnMetadata!.isBackground;
    const isRootType = rootSelectedTestScenarioDataObject.id === userSelectedTestScenarioObject.id;
    const expressionAlias = isRootType
      ? "expression </>"
      : userSelectedTestScenarioObject.id.replace(userSelectedTestScenarioObject.expressionElements[0] + ".", "");
    const factMappingValueType = isRootType ? "EXPRESSION" : "NOT_EXPRESSION";

    testScenarioEditorStoreApi.setState((state) => {
      const factMappings = isBackground
        ? state.scesim.model.ScenarioSimulationModel.background.scesimModelDescriptor.factMappings.FactMapping!
        : state.scesim.model.ScenarioSimulationModel.simulation.scesimModelDescriptor.factMappings.FactMapping!;
      const factMappingValuesTypes = isBackground
        ? state.scesim.model.ScenarioSimulationModel.background.scesimData.BackgroundData!
        : state.scesim.model.ScenarioSimulationModel.simulation.scesimData.Scenario!;

      const { updatedFactMapping } = updateColumn({
        className: userSelectedTestScenarioObject.className!,
        expressionAlias: expressionAlias,
        expressionElementsSteps: userSelectedTestScenarioObject.expressionElements,
        expressionIdentifierName: selectedColumnMetadata!.factMapping.expressionIdentifier.name?.__$$text,
        expressionIdentifierType: selectedColumnMetadata!.factMapping.expressionIdentifier.type?.__$$text,
        factMappings: factMappings,
        factClassName: rootSelectedTestScenarioDataObject.className!,
        factIdentifierClassName: selectedColumnMetadata!.factMapping.factIdentifier.className?.__$$text,
        factIdentifierName: selectedColumnMetadata!.factMapping.factIdentifier.name?.__$$text,
        factMappingValuesTypes: factMappingValuesTypes,
        factMappingValueType: factMappingValueType,
        factName: rootSelectedTestScenarioDataObject.name,
        genericTypes: userSelectedTestScenarioObject.collectionGenericType ?? [],
        selectedColumnIndex: selectedColumnMetadata!.index,
      });

      state.dispatch(state).table.updateSelectedColumn({
        factMapping: updatedFactMapping,
        index: selectedColumnMetadata!.index,
        isBackground: isBackground,
      });
    });
  }, [dataObjects, selectedColumnMetadata, testScenarioEditorStoreApi, treeViewStatus.activeItems]);

  const onClearSelectionClicked = useCallback((_event) => {
    setTreeViewStatus((prev) => {
      return {
        ...prev,
        activeItems: [],
      };
    });
  }, []);

  const onSearchTreeView = useCallback(
    (event) =>
      setTreeViewStatus((prev) => {
        return {
          ...prev,
          searchKey: event.target.value,
        };
      }),
    []
  );

  const onSelectTreeViewItem = useCallback((_event, treeViewItem: TestScenarioTreeViewDataItem) => {
    setTreeViewStatus((prev) => {
      return {
        ...prev,
        activeItems: [treeViewItem],
      };
    });
  }, []);

  const treeViewSearchToolbar = (
    <Toolbar style={{ padding: 0 }}>
      <ToolbarContent style={{ padding: 0 }}>
        <ToolbarItem widths={{ default: "100%" }}>
          <TreeViewSearch
            disabled={dataSelectorStatus !== TestScenarioDataSelectorState.ENABLED}
            id="input-search"
            name="search-input"
            onSearch={onSearchTreeView}
            value={treeViewStatus.searchKey}
          />
        </ToolbarItem>
      </ToolbarContent>
    </Toolbar>
  );

  return (
    <Stack>
      <StackItem>
        <Text className="kie-scesim-editor-drawer-data-objects--text">
          {testScenarioType === "DMN"
            ? i18n.drawer.dataSelector.descriptionDMN
            : i18n.drawer.dataSelector.descriptionRule}
          <Tooltip
            content={
              testScenarioType === "DMN"
                ? i18n.drawer.dataSelector.dataObjectsDescriptionDMN
                : i18n.drawer.dataSelector.dataObjectsDescriptionRule
            }
          >
            <Icon className={"kie-scesim-editor-drawer-data-objects--info-icon"} size="sm" status="info">
              <HelpIcon />
            </Icon>
          </Tooltip>
        </Text>
      </StackItem>
      <Divider />
      <StackItem isFilled>
        <div className={"kie-scesim-editor-drawer-data-objects--selector"}>
          {(treeViewEmptyStatus.visibility === "visible" && (
            <div aria-disabled={true}>
              <TreeView
                activeItems={treeViewStatus.activeItems}
                allExpanded={allExpanded || treeViewStatus.isExpanded}
                className={
                  dataSelectorStatus !== TestScenarioDataSelectorState.DISABLED
                    ? undefined
                    : "kie-scesim-editor-drawer-data-objects--selector-disabled"
                }
                data={filteredItems}
                hasBadges
                hasSelectableNodes
                onSelect={onSelectTreeViewItem}
                toolbar={treeViewSearchToolbar}
              />
            </div>
          )) ||
            (treeViewEmptyStatus.visibility === "hidden" && (
              <Bullseye>
                <EmptyState>
                  <EmptyStateHeader
                    titleText={<>{treeViewEmptyStatus.title}</>}
                    icon={<EmptyStateIcon icon={treeViewEmptyStatus.icon} />}
                    headingLevel="h4"
                  />
                  <EmptyStateBody>{treeViewEmptyStatus.description}</EmptyStateBody>
                </EmptyState>
              </Bullseye>
            )) ||
            (treeViewEmptyStatus.visibility === "loading" && (
              <Bullseye style={{ paddingTop: "10px" }}>
                <Spinner aria-label="Data Objects loading" />
              </Bullseye>
            ))}
        </div>
      </StackItem>
      <Divider />
      <StackItem>
        <div className={"kie-scesim-editor-drawer-data-objects--button-container"}>
          <Tooltip content={insertDataObjectButtonStatus.message}>
            <Button
              isAriaDisabled={!insertDataObjectButtonStatus.enabled}
              onClick={onInsertDataObjectClick}
              variant="primary"
            >
              {i18n.drawer.dataSelector.insertDataObject}
            </Button>
          </Tooltip>
          <Button
            isDisabled={
              treeViewStatus.activeItems.length !== 1 || dataSelectorStatus !== TestScenarioDataSelectorState.ENABLED
            }
            onClick={onClearSelectionClicked}
            variant="secondary"
          >
            {i18n.drawer.dataSelector.clearSelection}
          </Button>
          <Button
            isDisabled={
              filteredItems.length < 1 ||
              treeViewStatus.isExpanded ||
              dataSelectorStatus !== TestScenarioDataSelectorState.ENABLED
            }
            onClick={onAllExpandedToggle}
            variant="link"
          >
            {allExpanded ? i18n.drawer.dataSelector.collapseAll : i18n.drawer.dataSelector.expandAll}
          </Button>
        </div>
      </StackItem>
    </Stack>
  );
}