You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@atlas.apache.org by pp...@apache.org on 2026/05/06 09:48:08 UTC
(atlas) branch feature/pr-2-dashboard-search-filterquery-common created (now 5cbec482f)
This is an automated email from the ASF dual-hosted git repository.
pawarprasad123 pushed a change to branch feature/pr-2-dashboard-search-filterquery-common
in repository https://gitbox.apache.org/repos/asf/atlas.git
at 5cbec482f ATLAS-5286: ATLAS UI: Dashboard - Filter query and shared view helpers changes
This branch includes the following new commits:
new 5cbec482f ATLAS-5286: ATLAS UI: Dashboard - Filter query and shared view helpers changes
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
(atlas) 01/01: ATLAS-5286: ATLAS UI: Dashboard - Filter query and shared view helpers changes
Posted by pp...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
pawarprasad123 pushed a commit to branch feature/pr-2-dashboard-search-filterquery-common
in repository https://gitbox.apache.org/repos/asf/atlas.git
commit 5cbec482f5c5a50d9fcf500cc94a4a4a534e625b
Author: Prasad Pawar <pr...@freestoneinfotech.com>
AuthorDate: Wed May 6 15:14:56 2026 +0530
ATLAS-5286: ATLAS UI: Dashboard - Filter query and shared view helpers changes
---
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 };
+};