This is an automated email from the ASF dual-hosted git repository.

pawarprasad123 pushed a commit to branch feature/pr-9-License-added-forNewfile
in repository https://gitbox.apache.org/repos/asf/atlas.git

commit 47e3c47b514d54e1d08df95d93b2e65443c5af85
Author: Prasad Pawar <[email protected]>
AuthorDate: Fri May 8 12:51:25 2026 +0530

    ATLAS-5286: ATLAS UI: Dashboard - Filter query and  shared view helpers 
changes (#615)
---
 dashboard/src/components/FilterQuery.tsx         | 197 +++++++++++++++++------
 dashboard/src/utils/CommonViewFunction.ts        |  17 +-
 dashboard/src/utils/filterUrlCriterionRemoval.ts |  81 ++++++++++
 3 files changed, 246 insertions(+), 49 deletions(-)

diff --git a/dashboard/src/components/FilterQuery.tsx 
b/dashboard/src/components/FilterQuery.tsx
index 69ef98fd2..20351c33a 100644
--- a/dashboard/src/components/FilterQuery.tsx
+++ b/dashboard/src/components/FilterQuery.tsx
@@ -22,8 +22,12 @@ import { attributeFilter } from "@utils/CommonViewFunction";
 import {
   queryBuilderDateRangeUIValueToAPI,
   systemAttributes,
-  filterQueryValue
+  filterQueryValue,
+  getDisplayOperator
 } from "@utils/Enum";
+import { removeCriterionFromFilterUrl } from 
"@utils/filterUrlCriterionRemoval";
+import type { ParsedApiFilter } from "@utils/filterUrlCriterionRemoval";
+import { dateTimeFormat } from "@utils/Global";
 import moment from "moment";
 import { useLocation, useNavigate } from "react-router-dom";
 import { globalSearchFilterInitialQuery } from "@utils/Utils";
@@ -45,13 +49,18 @@ export const FilterQuery = ({ value }: any) => {
           </Typography>
         );
       } else {
-        if (obj.type === "date" || obj.id == "createTime") {
+        const isDateAttr =
+          obj.type === "date" ||
+          obj.id === "createTime" ||
+          obj.id === "__timestamp" ||
+          obj.id === "__modificationTimestamp";
+        if (isDateAttr) {
           if (queryBuilderDateRangeUIValueToAPI[obj.value]) {
             obj.value = queryBuilderDateRangeUIValueToAPI[obj.value];
+          } else if (/^\d+$/.test(String(obj.value))) {
+            obj.value = `${moment(Number(obj.value)).format(dateTimeFormat)} 
(${moment.tz(moment.tz.guess()).zoneAbbr()})`;
           } else {
-            obj.value = `${obj.value} (${moment
-              .tz(moment.tz.guess())
-              .zoneAbbr()})`;
+            obj.value = `${obj.value} 
(${moment.tz(moment.tz.guess()).zoneAbbr()})`;
           }
         }
 
@@ -60,6 +69,7 @@ export const FilterQuery = ({ value }: any) => {
             color="primary"
             className="chip-items"
             data-type={type}
+            data-rule-key={key}
             data-id={`${obj.id}${key}`}
             data-cy={`${obj.id}${key}`}
             label={
@@ -71,7 +81,7 @@ export const FilterQuery = ({ value }: any) => {
                       : obj.id}
                   </Typography>
                   <Typography className="operator" fontWeight="600">
-                    {obj.operator}{" "}
+                    {getDisplayOperator(obj.operator)}{" "}
                   </Typography>
                   <Typography className="searchValue">
                     {filterQueryValue[obj.id]
@@ -97,9 +107,138 @@ export const FilterQuery = ({ value }: any) => {
 
   const clearQueryAttr = (e: any) => {
     const searchParams = new URLSearchParams(location.search);
-    const currentType: any = e?.target
-      ?.closest("[data-type]")
-      ?.getAttribute("data-type");
+    const chipEl = (e?.target as HTMLElement)?.closest?.(
+      "[data-type]"
+    ) as HTMLElement | null;
+    const currentType = chipEl?.getAttribute("data-type");
+    const ruleKeyRaw = chipEl?.getAttribute("data-rule-key");
+
+    const navigateAfterChange = (sp: URLSearchParams) => {
+      const meaningfulFilterParams = [
+        "type",
+        "tag",
+        "query",
+        "term",
+        "relationshipName",
+        "entityFilters",
+        "tagFilters",
+        "relationshipFilters",
+        "excludeST",
+        "excludeSC",
+        "includeDE"
+      ];
+      const hasMeaningfulFilters = meaningfulFilterParams.some((param) =>
+        sp.has(param)
+      );
+      if (!hasMeaningfulFilters) {
+        navigate({
+          pathname: "/search"
+        });
+      } else if ([...sp]?.length <= 1) {
+        navigate({
+          pathname: "/search"
+        });
+      } else {
+        navigate({
+          pathname: "/search/searchResult",
+          search: sp.toString()
+        });
+      }
+    };
+
+    const syncFilterParamGlobalState = (
+      paramKey: "entityFilters" | "tagFilters" | "relationshipFilters",
+      urlValue: string | null
+    ) => {
+      if (!urlValue) {
+        globalSearchFilterInitialQuery.setQuery({ [paramKey]: [] });
+        return;
+      }
+      const api = attributeFilter.extractUrl({
+        value: urlValue,
+        apiObj: true
+      }) as ParsedApiFilter | null;
+      if (!api?.criterion || !Array.isArray(api.criterion)) {
+        globalSearchFilterInitialQuery.setQuery({ [paramKey]: [] });
+        return;
+      }
+      globalSearchFilterInitialQuery.setQuery({
+        [paramKey]: {
+          combinator: String(api.condition || "AND").toLowerCase(),
+          rules: api.criterion.map((rule: any, i: number) => ({
+            id: `url-rule-${i}`,
+            field: rule.attributeName,
+            operator: getDisplayOperator(rule.operator) || rule.operator,
+            value: rule.attributeValue,
+            ...(rule.type ? { type: rule.type } : {})
+          }))
+        }
+      });
+    };
+
+    const isFilterCriterionChip =
+      ruleKeyRaw !== null &&
+      ruleKeyRaw !== "" &&
+      (currentType === "entityFilters" ||
+        currentType === "tagFilters" ||
+        currentType === "relationshipFilters");
+
+    if (isFilterCriterionChip) {
+      const idx = Number.parseInt(ruleKeyRaw as string, 10);
+      if (Number.isNaN(idx)) {
+        return;
+      }
+      const paramKey = currentType as
+        | "entityFilters"
+        | "tagFilters"
+        | "relationshipFilters";
+      const raw = searchParams.get(paramKey);
+      if (!raw) {
+        return;
+      }
+      const allowSimplify = paramKey === "entityFilters";
+      const result = removeCriterionFromFilterUrl(raw, idx, allowSimplify);
+      if (!result) {
+        return;
+      }
+      if (result.kind === "empty") {
+        searchParams.delete(paramKey);
+        globalSearchFilterInitialQuery.setQuery({ [paramKey]: [] });
+        navigateAfterChange(searchParams);
+        return;
+      }
+      if (result.kind === "singleTypeName") {
+        searchParams.set("type", result.typeName);
+        searchParams.delete("entityFilters");
+        if (searchParams.get("includeDE") === "true") {
+          const delUrl = attributeFilter.generateUrl({
+            value: {
+              condition: "AND",
+              criterion: [
+                {
+                  attributeName: "__state",
+                  operator: "eq",
+                  attributeValue: "DELETED"
+                }
+              ]
+            }
+          });
+          if (delUrl) {
+            searchParams.set("entityFilters", delUrl);
+          }
+        }
+        syncFilterParamGlobalState(
+          "entityFilters",
+          searchParams.get("entityFilters")
+        );
+        navigateAfterChange(searchParams);
+        return;
+      }
+      searchParams.set(paramKey, result.url);
+      syncFilterParamGlobalState(paramKey, result.url);
+      navigateAfterChange(searchParams);
+      return;
+    }
 
     if (currentType == "term") {
       searchParams.delete("gtype");
@@ -127,43 +266,11 @@ export const FilterQuery = ({ value }: any) => {
       globalSearchFilterInitialQuery.setQuery({ [currentType]: [] });
     }
 
-    searchParams.delete(currentType);
-
-    // Check if there are any meaningful filters left after removal
-    const meaningfulFilterParams = [
-      "type",
-      "tag",
-      "query",
-      "term",
-      "relationshipName",
-      "entityFilters",
-      "tagFilters",
-      "relationshipFilters",
-      "excludeST",
-      "excludeSC",
-      "includeDE"
-    ];
-
-    const hasMeaningfulFilters = meaningfulFilterParams.some((param) =>
-      searchParams.has(param)
-    );
-
-    // If no meaningful filters remain, navigate to clear all (like Clear 
button)
-    if (!hasMeaningfulFilters) {
-      navigate({
-        pathname: "/search"
-      });
-    } else if ([...searchParams]?.length <= 1) {
-      // Only searchType or other system params remain
-      navigate({
-        pathname: "/search"
-      });
-    } else {
-      navigate({
-        pathname: "/search/searchResult",
-        search: searchParams.toString()
-      });
+    if (currentType) {
+      searchParams.delete(currentType);
     }
+
+    navigateAfterChange(searchParams);
   };
 
   if (value.type) {
diff --git a/dashboard/src/utils/CommonViewFunction.ts 
b/dashboard/src/utils/CommonViewFunction.ts
index 3760de5a5..d58357bfd 100644
--- a/dashboard/src/utils/CommonViewFunction.ts
+++ b/dashboard/src/utils/CommonViewFunction.ts
@@ -193,13 +193,19 @@ export const attributeFilter = {
             let temp = obj.split("::") || obj.split("|" + spliter + "|");
             let rule = {};
             if (apiObj) {
+              const attrName = temp[0];
+              const isDateAttr =
+                temp[3] === "date" ||
+                attrName === "__timestamp" ||
+                attrName === "__modificationTimestamp" ||
+                attrName === "createTime";
               rule = {
-                attributeName: temp[0],
+                attributeName: attrName,
                 operator: mapUiOperatorToAPI(temp[1]),
                 attributeValue: temp[2]?.trim()
               };
               rule.attributeValue =
-                rule.type === "date" && formatDate && 
rule.attributeValue.length
+                isDateAttr && formatDate && rule.attributeValue?.length
                   ? formatedDate({
                       date: parseInt(rule.attributeValue),
                       zone: false
@@ -231,9 +237,12 @@ export const attributeFilter = {
                     rule.value;
                 }
               } else if (
-                (rule.type === "date" || rule.attributeName == "createTime") &&
+                (rule.type === "date" ||
+                  rule.attributeName === "createTime" ||
+                  rule.id === "__timestamp" ||
+                  rule.id === "__modificationTimestamp") &&
                 formatDate &&
-                rule.value.length
+                rule.value?.length
               ) {
                 rule.value = formatedDate({
                   date: parseInt(rule.value),
diff --git a/dashboard/src/utils/filterUrlCriterionRemoval.ts 
b/dashboard/src/utils/filterUrlCriterionRemoval.ts
new file mode 100644
index 000000000..746fe7998
--- /dev/null
+++ b/dashboard/src/utils/filterUrlCriterionRemoval.ts
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { attributeFilter } from "./CommonViewFunction";
+
+export type RemoveCriterionResult =
+       | { kind: "empty" }
+       | { kind: "url"; url: string }
+       | { kind: "singleTypeName"; typeName: string };
+
+type ApiFilterCriterionRow = {
+       attributeName: string;
+       operator: string;
+       attributeValue: string;
+       type?: string;
+};
+
+/** Narrowing shape for `attributeFilter.extractUrl(..., { apiObj: true })`. */
+export type ParsedApiFilter = {
+       condition: string;
+       criterion: ApiFilterCriterionRow[];
+};
+
+/**
+ * Drops one criterion from a serialized filter URL (entity / tag / 
relationship).
+ * When allowTypeParamSimplification is true and exactly one __typeName eq 
remains,
+ * returns singleTypeName so the UI can use type=<typedef> (matches sidebar 
search).
+ */
+export const removeCriterionFromFilterUrl = (
+       filterUrl: string,
+       index: number,
+       allowTypeParamSimplification = false
+): RemoveCriterionResult | null => {
+       if (!filterUrl || index < 0) {
+               return null;
+       }
+       const parsed = attributeFilter.extractUrl({
+               value: filterUrl,
+               apiObj: true
+       }) as ParsedApiFilter | null;
+       if (!parsed?.criterion || !Array.isArray(parsed.criterion)) {
+               return null;
+       }
+       const crit = [...parsed.criterion];
+       if (index >= crit.length) {
+               return null;
+       }
+       crit.splice(index, 1);
+       if (crit.length === 0) {
+               return { kind: "empty" };
+       }
+       if (
+               allowTypeParamSimplification &&
+               crit.length === 1 &&
+               crit[0].attributeName === "__typeName" &&
+               String(crit[0].operator).toLowerCase() === "eq"
+       ) {
+               return { kind: "singleTypeName", typeName: 
String(crit[0].attributeValue) };
+       }
+       const url = attributeFilter.generateUrl({
+               value: { condition: parsed.condition, criterion: crit }
+       });
+       if (!url) {
+               return { kind: "empty" };
+       }
+       return { kind: "url", url };
+};

Reply via email to