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

pawarprasad123 pushed a commit to branch ATLAS-5246-v1
in repository https://gitbox.apache.org/repos/asf/atlas.git

commit 1a282a3b156fafd99a3b71c8cca1fc57ce015f1b
Author: Prasad Pawar <[email protected]>
AuthorDate: Fri May 8 12:57:40 2026 +0530

    ATLAS-5251: ATLAS UI : Dashboard - Latest Entities List (#619)
---
 dashboard/src/api/apiMethods/searchApiMethod.ts    |  62 ++++-
 dashboard/src/utils/dashboardSearchUtils.ts        | 189 ++++++++++++++
 .../views/DashboardOverview/LatestEntitiesList.tsx | 273 +++++++++++++++++++++
 .../DashboardOverview/LatestEntitiesSkeleton.tsx   |  53 ++++
 .../DashboardOverview/latestEntitiesList.utils.ts  |  64 +++++
 dashboard/src/views/SearchResult/SearchResult.tsx  |  24 +-
 6 files changed, 658 insertions(+), 7 deletions(-)

diff --git a/dashboard/src/api/apiMethods/searchApiMethod.ts 
b/dashboard/src/api/apiMethods/searchApiMethod.ts
index 11a061e4f..8cc58f041 100644
--- a/dashboard/src/api/apiMethods/searchApiMethod.ts
+++ b/dashboard/src/api/apiMethods/searchApiMethod.ts
@@ -56,10 +56,62 @@ const getRelationShipV2 = (params: { params: Record<string, 
unknown> }) => {
   return fetchApi(url, { method: "GET" });
 };
 
+/** Atlas `SearchParameters.sortBy` / `sortOrder` (see `SortOrder` enum: 
DESCENDING). */
+const LATEST_ENTITIES_TIMESTAMP_SORT = "__timestamp" as const;
+
+type LatestEntitiesSearchOptions = {
+  limit: number;
+  includeSubClassifications: boolean;
+};
+
+/**
+ * Request `__timestamp` so sort + “Created … ago” work. Do not set
+ * `excludeHeaderAttributes`: for `_ALL_ENTITY_TYPES`, Atlas validates each
+ * `attributes` entry against `__ENTITY_ROOT` and rejects `name` / 
`qualifiedName`
+ * / `guid` (see `excludeHeaderAttributesAllEntityType` in Atlas tests). Normal
+ * headers then include name, guid, typeName like the main basic search.
+ */
+const buildLatestEntitiesBasicBody = (opts: LatestEntitiesSearchOptions) => {
+  return {
+    typeName: "_ALL_ENTITY_TYPES",
+    excludeDeletedEntities: true,
+    includeClassificationAttributes: true,
+    includeSubTypes: true,
+    includeSubClassifications: opts.includeSubClassifications,
+    limit: opts.limit,
+    offset: 0,
+    tagFilters: null,
+    entityFilters: null,
+    classification: null,
+    termName: null,
+    relationshipFilters: null,
+    attributes: ["__timestamp"],
+    sortBy: LATEST_ENTITIES_TIMESTAMP_SORT,
+    sortOrder: "DESCENDING",
+  };
+};
+
+/**
+ * Dashboard card only: newest entities by `__timestamp`, no entity filters,
+ * sub-classifications off, full entity headers for name/guid/type.
+ */
+const getLatestEntities = () => {
+  return getBasicSearchResult(
+    {
+      data: buildLatestEntitiesBasicBody({
+        limit: 7,
+        includeSubClassifications: false,
+      }),
+    },
+    "basic"
+  );
+};
+
 export {
-  getBasicSearchResult,
-  getRelationShipResult,
-  getGlobalSearchResult,
-  getRelationShip,
-  getRelationShipV2
+       getBasicSearchResult,
+       getRelationShipResult,
+       getGlobalSearchResult,
+       getRelationShip,
+       getRelationShipV2,
+       getLatestEntities
 };
diff --git a/dashboard/src/utils/dashboardSearchUtils.ts 
b/dashboard/src/utils/dashboardSearchUtils.ts
new file mode 100644
index 000000000..15ebdf699
--- /dev/null
+++ b/dashboard/src/utils/dashboardSearchUtils.ts
@@ -0,0 +1,189 @@
+/*
+ * 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 type { NavigateFunction } from "react-router-dom";
+import { attributeFilter } from "@utils/CommonViewFunction";
+
+export type DashboardSearchType =
+       | "all_entities"
+       | "all_classifications"
+       | "entity_status"
+       | "entity_type";
+
+export interface DashboardSearchParams {
+       type?: string;
+       tag?: string;
+       includeDE?: boolean;
+       entityFilters?: {
+               condition: string;
+               criterion: Array<{ attributeName: string; operator: string; 
attributeValue: string }>;
+       };
+}
+
+const buildSearchParams = (
+       type: DashboardSearchType,
+       extra?: Partial<DashboardSearchParams>
+): URLSearchParams => {
+       const params = new URLSearchParams();
+       params.set("searchType", "basic");
+
+       if (type === "all_classifications") {
+               params.set("tag", extra?.tag ?? "_ALL_CLASSIFICATION_TYPES");
+               params.set("type", "");
+       } else {
+               params.set("type", extra?.type ?? "_ALL_ENTITY_TYPES");
+       }
+       if (extra?.includeDE) {
+               params.set("includeDE", "true");
+       }
+       if (extra?.entityFilters) {
+               const urlStr = attributeFilter.generateUrl({ value: 
extra.entityFilters });
+               if (urlStr) params.set("entityFilters", urlStr);
+       }
+       return params;
+};
+
+export const navigateToTaggedSearch = (navigate: NavigateFunction): void => {
+       navigateToSearch(navigate, "all_classifications");
+};
+
+export const navigateToClassificationSearch = (
+       navigate: NavigateFunction,
+       tagName: string
+): void => {
+       navigateToSearch(navigate, "all_classifications", { tag: tagName });
+};
+
+/** "View All" from Latest Entities card: basic all-types search, newest 
first. */
+export const navigateToLatestEntitiesSearch = (navigate: NavigateFunction): 
void => {
+       const params = buildSearchParams("all_entities");
+       params.set("pageLimit", "25");
+       params.set("pageOffset", "0");
+       params.set("sortBy", "__timestamp");
+       params.set("sortOrder", "DESCENDING");
+       /* Same as dashboard card: request system create time in the 
basic-search body. */
+       params.set("attributes", "__timestamp");
+       navigate({ pathname: "/search/searchResult", search: params.toString() 
});
+};
+
+export const navigateToEntityTypeSearch = (
+       navigate: NavigateFunction,
+       typeName: string,
+       includeDeleted: boolean
+): void => {
+       const extra: Partial<DashboardSearchParams> = {
+               type: typeName,
+               includeDE: includeDeleted
+       };
+       if (includeDeleted) {
+               extra.entityFilters = {
+                       condition: "AND",
+                       criterion: [{ attributeName: "__state", operator: "eq", 
attributeValue: "DELETED" }]
+               };
+       }
+       navigateToSearch(navigate, "entity_type", extra);
+};
+
+/** Search all entity typedefs that belong to one service-type bucket (sidebar 
grouping). */
+export const navigateToServiceTypeEntitySearch = (
+       navigate: NavigateFunction,
+       typeNames: string[],
+       includeDeleted: boolean
+): void => {
+       const cleaned = [...new Set(typeNames.filter(Boolean))].sort((a, b) => 
a.localeCompare(b));
+       if (cleaned.length === 0) {
+               navigateToSearch(navigate, "all_entities");
+               return;
+       }
+       if (cleaned.length === 1) {
+               navigateToEntityTypeSearch(navigate, cleaned[0], 
includeDeleted);
+               return;
+       }
+       const criterion = cleaned.map((typeName) => ({
+               attributeName: "__typeName",
+               operator: "eq",
+               attributeValue: typeName,
+       }));
+       navigateToSearch(navigate, "all_entities", {
+               type: "_ALL_ENTITY_TYPES",
+               includeDE: includeDeleted,
+               entityFilters: { condition: "OR", criterion },
+       });
+};
+
+export const navigateToSearch = (
+       navigate: NavigateFunction,
+       type: DashboardSearchType,
+       extra?: Partial<DashboardSearchParams>
+): void => {
+       const search = buildSearchParams(type, extra).toString();
+       navigate({ pathname: "/search/searchResult", search });
+};
+
+/** Basic search using free-text `query` (e.g. entity typedef name). */
+export const navigateToBasicTextQuery = (
+       navigate: NavigateFunction,
+       query: string
+): void => {
+       const params = new URLSearchParams();
+       params.set("searchType", "basic");
+       params.set("query", query.trim());
+       params.set("pageLimit", "25");
+       params.set("pageOffset", "0");
+       navigate({ pathname: "/search/searchResult", search: params.toString() 
});
+};
+
+export const navigateToClassificationDetailPage = (
+       navigate: NavigateFunction,
+       classificationName: string
+): void => {
+       const sp = new URLSearchParams();
+       sp.set("tag", classificationName);
+       navigate({
+               pathname: 
`/tag/tagAttribute/${encodeURIComponent(classificationName)}`,
+               search: sp.toString()
+       });
+};
+
+export const navigateToGlossaryTermDetailPage = (
+       navigate: NavigateFunction,
+       args: {
+               termGuid: string;
+               termId: string;
+               glossaryGuid: string;
+               parentName: string;
+       }
+): void => {
+       const sp = new URLSearchParams();
+       sp.set("gid", args.glossaryGuid);
+       sp.set("term", `${args.termId}@${args.parentName}`);
+       sp.set("gtype", "term");
+       sp.set("viewType", "term");
+       sp.set("guid", args.termGuid);
+       sp.set("searchType", "basic");
+       navigate({
+               pathname: `/glossary/${args.termGuid}`,
+               search: sp.toString()
+       });
+};
+
+export const navigateToBusinessMetadataDetailPage = (
+       navigate: NavigateFunction,
+       bmGuid: string
+): void => {
+       navigate({ pathname: `/administrator/businessMetadata/${bmGuid}` });
+};
diff --git a/dashboard/src/views/DashboardOverview/LatestEntitiesList.tsx 
b/dashboard/src/views/DashboardOverview/LatestEntitiesList.tsx
new file mode 100644
index 000000000..2d80a5a1f
--- /dev/null
+++ b/dashboard/src/views/DashboardOverview/LatestEntitiesList.tsx
@@ -0,0 +1,273 @@
+/*
+ * 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 { memo, useCallback } from "react";
+import { Paper, Stack, Typography, Link, List, ListItem, Box } from 
"@mui/material";
+import { Link as RouterLink } from "react-router-dom";
+import moment from "moment";
+import { useNavigate } from "react-router-dom";
+import { navigateToLatestEntitiesSearch } from "@utils/dashboardSearchUtils";
+import type { LatestEntityRowModel } from "./latestEntitiesList.utils";
+import {
+       resolveLatestEntityDisplayName,
+       resolveLatestEntityGuid,
+       resolveLatestEntityTypeName
+} from "./latestEntitiesList.utils";
+
+interface EntityItem extends LatestEntityRowModel {
+       createTime?: number | Date | string;
+       attributes?: LatestEntityRowModel["attributes"] & {
+               __timestamp?: number | string | Record<string, unknown>;
+               createTime?: number | string;
+       };
+}
+
+interface LatestEntitiesListProps {
+       entities: EntityItem[];
+       isLoading?: boolean;
+       error?: string | null;
+}
+
+const INVALID_TS_LABEL = "Created today";
+
+const unwrapLongLike = (raw: unknown): unknown => {
+       if (raw == null || typeof raw !== "object" || Array.isArray(raw)) 
return raw;
+       const o = raw as Record<string, unknown>;
+       if (typeof o.$numberLong === "string" || typeof o.$numberLong === 
"number") {
+               return o.$numberLong;
+       }
+       if (typeof o.longValue === "string" || typeof o.longValue === "number") 
{
+               return o.longValue;
+       }
+       return raw;
+};
+
+/**
+ * Milliseconds since epoch, or null only when missing / unusable.
+ * Rejects 0 (epoch) to avoid "56 years ago". Accepts sec or ms numbers, ISO 
strings.
+ */
+const normalizeEntityTimestampMs = (raw: unknown): number | null => {
+       const v = unwrapLongLike(raw);
+       if (v == null) return null;
+       if (v instanceof Date) {
+               const t = v.getTime();
+               if (!Number.isFinite(t) || t <= 0) return null;
+               return moment(t).isValid() ? t : null;
+       }
+       if (typeof v === "string") {
+               const trimmed = v.trim();
+               if (trimmed === "") return null;
+               const n = Number(trimmed);
+               if (Number.isFinite(n) && n > 0) {
+                       const ms = n < 1e12 ? n * 1000 : n;
+                       if (moment(ms).isValid() && ms > 0) return ms;
+               }
+               const parsed = moment(trimmed);
+               if (parsed.isValid()) {
+                       const ms = parsed.valueOf();
+                       if (ms > 0) return ms;
+               }
+               return null;
+       }
+       if (typeof v === "number") {
+               if (!Number.isFinite(v) || v <= 0) return null;
+               const ms = v < 1e12 ? v * 1000 : v;
+               return moment(ms).isValid() && ms > 0 ? ms : null;
+       }
+       return null;
+};
+
+/** Use only `__timestamp` for relative "Created … ago" (not `createTime`, 
often 0 in API). */
+const getEntityTimestampRawForDisplay = (entity: EntityItem): unknown => {
+       const a = entity.attributes;
+       const top = entity as EntityItem & { __timestamp?: unknown };
+       return a?.__timestamp ?? top.__timestamp;
+};
+
+/** Valid timestamp only: seconds / minutes / hours for last 24h, then moment 
relative. */
+const formatCreatedRelativeFromMs = (ms: number): string => {
+       const now = Date.now();
+       const deltaMs = now - ms;
+       if (!Number.isFinite(deltaMs)) return INVALID_TS_LABEL;
+       if (deltaMs < 0) {
+               return `Created ${moment(ms).fromNow()}`;
+       }
+       const totalSec = Math.floor(deltaMs / 1000);
+       if (totalSec < 1) {
+               return "Created just now";
+       }
+       if (totalSec < 60) {
+               return totalSec === 1
+                       ? "Created 1 second ago"
+                       : `Created ${totalSec} seconds ago`;
+       }
+       const totalMin = Math.floor(totalSec / 60);
+       if (totalMin < 60) {
+               return totalMin === 1
+                       ? "Created 1 minute ago"
+                       : `Created ${totalMin} minutes ago`;
+       }
+       const totalHr = Math.floor(totalMin / 60);
+       if (totalHr < 24) {
+               return totalHr === 1
+                       ? "Created 1 hour ago"
+                       : `Created ${totalHr} hours ago`;
+       }
+       return `Created ${moment(ms).fromNow()}`;
+};
+
+const formatRelativeTime = (raw: unknown): string => {
+       const ms = normalizeEntityTimestampMs(raw);
+       if (ms == null) return INVALID_TS_LABEL;
+       return formatCreatedRelativeFromMs(ms);
+};
+
+const LatestEntitiesList = memo(({ entities, isLoading, error }: 
LatestEntitiesListProps) => {
+       const navigate = useNavigate();
+
+       const handleViewAll = useCallback(() => {
+               navigateToLatestEntitiesSearch(navigate);
+       }, [navigate]);
+
+       if (isLoading) return null;
+
+       return (
+               <Paper
+                       elevation={1}
+                       sx={{
+                               padding: 2,
+                               borderRadius: 2,
+                               minHeight: 340,
+                               minWidth: 0,
+                               width: "100%",
+                               flex: 1,
+                               boxSizing: "border-box",
+                               transition: "box-shadow 0.3s ease",
+                               "&:hover": { boxShadow: 4 }
+                       }}
+               >
+                       <Box sx={{ pb: 2, borderBottom: "1px solid", 
borderColor: "divider" }}>
+                               <Stack direction="row" 
justifyContent="space-between" alignItems="center">
+                                       <Typography sx={{ fontSize: "1rem", 
fontWeight: 600, color: "#1a1a1a" }}>
+                                               Latest Entities Created
+                                       </Typography>
+                                       <Link
+                                               component="button"
+                                               onClick={handleViewAll}
+                                               sx={{
+                                                       fontSize: "0.875rem",
+                                                       cursor: "pointer",
+                                                       textDecoration: "none",
+                                                       color: "primary.main"
+                                               }}
+                                               aria-label="View all entities"
+                                       >
+                                               View All
+                                       </Link>
+                               </Stack>
+                       </Box>
+                       {error ? (
+                               <Stack alignItems="center" 
justifyContent="center" height={200} sx={{ pt: 2 }}>
+                                       <Typography variant="body2" 
color="error">
+                                               {error}
+                                       </Typography>
+                               </Stack>
+                       ) : !entities || entities.length === 0 ? (
+                               <Stack alignItems="center" 
justifyContent="center" height={200} sx={{ pt: 2 }}>
+                                       <Typography variant="body2" 
color="text.secondary">
+                                               No recent entities
+                                       </Typography>
+                               </Stack>
+                       ) : (
+                               <List disablePadding sx={{ pt: 2 }}>
+                                       {entities.slice(0, 7).map((entity) => {
+                                               const displayName = 
resolveLatestEntityDisplayName(entity);
+                                               const entityGuid = 
resolveLatestEntityGuid(entity);
+                                               const typeName = 
resolveLatestEntityTypeName(entity);
+                                               const timestamp = 
getEntityTimestampRawForDisplay(entity);
+                                               const detailHref = entityGuid
+                                                       ? 
`/detailPage/${entityGuid}`
+                                                       : undefined;
+
+                                               return (
+                                                       <ListItem
+                                                               key={entityGuid 
|| displayName}
+                                                               disablePadding
+                                                               sx={{
+                                                                       py: 1,
+                                                                       
borderBottom: "1px solid",
+                                                                       
borderColor: "divider",
+                                                                       
"&:last-child": { borderBottom: "none" }
+                                                               }}
+                                                       >
+                                                               <Stack 
width="100%" direction="row" justifyContent="space-between" alignItems="center">
+                                                                       <Stack 
direction="row" spacing={0.5} alignItems="center" flexWrap="wrap" flex={1} 
minWidth={0} mr={1}>
+                                                                               
{detailHref ? (
+                                                                               
        <Link
+                                                                               
                component={RouterLink}
+                                                                               
                to={detailHref}
+                                                                               
                underline="hover"
+                                                                               
                color="primary"
+                                                                               
                sx={{
+                                                                               
                        fontSize: "0.875rem",
+                                                                               
                        overflow: "hidden",
+                                                                               
                        textOverflow: "ellipsis",
+                                                                               
                        cursor: "pointer",
+                                                                               
                        maxWidth: "100%"
+                                                                               
                }}
+                                                                               
        >
+                                                                               
                {displayName}
+                                                                               
        </Link>
+                                                                               
) : (
+                                                                               
        <Typography
+                                                                               
                component="span"
+                                                                               
                sx={{
+                                                                               
                        fontSize: "0.875rem",
+                                                                               
                        fontWeight: 500,
+                                                                               
                        color: "text.primary"
+                                                                               
                }}
+                                                                               
        >
+                                                                               
                {displayName}
+                                                                               
        </Typography>
+                                                                               
)}
+                                                                               
<Typography
+                                                                               
        component="span"
+                                                                               
        sx={{
+                                                                               
                fontSize: "0.875rem",
+                                                                               
                color: "#6c757d",
+                                                                               
                flexShrink: 0
+                                                                               
        }}
+                                                                               
>
+                                                                               
        ({typeName})
+                                                                               
</Typography>
+                                                                       </Stack>
+                                                                       
<Typography sx={{ fontSize: "0.8125rem", color: "#6c757d", flexShrink: 0, ml: 1 
}}>
+                                                                               
{formatRelativeTime(timestamp)}
+                                                                       
</Typography>
+                                                               </Stack>
+                                                       </ListItem>
+                                               );
+                                       })}
+                               </List>
+                       )}
+               </Paper>
+       );
+});
+
+LatestEntitiesList.displayName = "LatestEntitiesList";
+
+export default LatestEntitiesList;
diff --git a/dashboard/src/views/DashboardOverview/LatestEntitiesSkeleton.tsx 
b/dashboard/src/views/DashboardOverview/LatestEntitiesSkeleton.tsx
new file mode 100644
index 000000000..75e4f5960
--- /dev/null
+++ b/dashboard/src/views/DashboardOverview/LatestEntitiesSkeleton.tsx
@@ -0,0 +1,53 @@
+/*
+ * 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 { Paper, Stack, Box } from "@mui/material";
+import SkeletonLoader from "@components/SkeletonLoader";
+
+const LatestEntitiesSkeleton = () => (
+       <Paper
+               elevation={1}
+               sx={{
+                       padding: 2,
+                       borderRadius: 2,
+                       minHeight: 340,
+                       minWidth: 0,
+                       width: "100%",
+                       flex: 1,
+                       boxSizing: "border-box",
+                       transition: "box-shadow 0.3s ease",
+                       "&:hover": { boxShadow: 4 }
+               }}
+       >
+               <Box sx={{ pb: 2, borderBottom: "1px solid", borderColor: 
"divider" }}>
+                       <Stack direction="row" justifyContent="space-between" 
alignItems="center">
+                               <SkeletonLoader animation="wave" variant="text" 
count={1} width="60%" height={24} />
+                               <SkeletonLoader animation="wave" variant="text" 
count={1} width={60} height={20} />
+                       </Stack>
+               </Box>
+               <Stack spacing={1} sx={{ pt: 2 }}>
+                       {[1, 2, 3, 4, 5, 6, 7].map((i) => (
+                               <Stack key={i} direction="row" 
justifyContent="space-between" alignItems="center" sx={{ py: 0.5 }}>
+                                       <SkeletonLoader animation="wave" 
variant="text" count={1} width={`${70 + (i % 3) * 10}%`} height={20} />
+                                       <SkeletonLoader animation="wave" 
variant="text" count={1} width={80} height={18} />
+                               </Stack>
+                       ))}
+               </Stack>
+       </Paper>
+);
+
+export default LatestEntitiesSkeleton;
diff --git a/dashboard/src/views/DashboardOverview/latestEntitiesList.utils.ts 
b/dashboard/src/views/DashboardOverview/latestEntitiesList.utils.ts
new file mode 100644
index 000000000..118be6b15
--- /dev/null
+++ b/dashboard/src/views/DashboardOverview/latestEntitiesList.utils.ts
@@ -0,0 +1,64 @@
+/*
+ * 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.
+ */
+
+/** Shape of basic-search entity headers used by Latest Entities card. */
+export interface LatestEntityRowModel {
+       guid?: string;
+       name?: string;
+       typeName?: string;
+       displayText?: string;
+       attributes?: {
+               name?: string;
+               qualifiedName?: string;
+               __guid?: string;
+       };
+}
+
+/**
+ * Match Search name column: top-level name/displayText and attributes.name /
+ * qualifiedName (Atlas may return any of these).
+ */
+export const resolveLatestEntityDisplayName = (
+       entity: LatestEntityRowModel
+): string => {
+       const n =
+               entity.name ??
+               entity.attributes?.name ??
+               entity.attributes?.qualifiedName ??
+               entity.displayText ??
+               entity.guid;
+       return typeof n === "string" && n.trim() !== "" ? n.trim() : "Unknown";
+};
+
+export const resolveLatestEntityGuid = (
+       entity: LatestEntityRowModel
+): string | undefined => {
+       const g =
+               entity.guid ??
+               (typeof entity.attributes?.__guid === "string"
+                       ? entity.attributes.__guid
+                       : undefined);
+       if (typeof g !== "string" || g.trim() === "") return undefined;
+       return g.trim();
+};
+
+export const resolveLatestEntityTypeName = (
+       entity: LatestEntityRowModel
+): string => {
+       const t = entity.typeName;
+       return typeof t === "string" && t.trim() !== "" ? t.trim() : "Entity";
+};
diff --git a/dashboard/src/views/SearchResult/SearchResult.tsx 
b/dashboard/src/views/SearchResult/SearchResult.tsx
index a026e1175..5d850d93a 100644
--- a/dashboard/src/views/SearchResult/SearchResult.tsx
+++ b/dashboard/src/views/SearchResult/SearchResult.tsx
@@ -71,6 +71,9 @@ interface Params {
   classification: any;
   termName: string | null;
   relationshipFilters?: string | null;
+  sortBy?: string;
+  sortOrder?: string;
+  excludeHeaderAttributes?: boolean;
 }
 
 let defaultColumnsName: Array<string> = [
@@ -155,6 +158,15 @@ const SearchResult = ({ classificationParams, 
glossaryTypeParams, hideFilters }:
           ? !searchParams.get("excludeST")
           : true,
         includeClassificationAttributes: true,
+        ...(!isEmpty(searchParams.get("sortBy")) && {
+          sortBy: searchParams.get("sortBy")
+        }),
+        ...(!isEmpty(searchParams.get("sortOrder")) && {
+          sortOrder: searchParams.get("sortOrder")
+        }),
+        ...(searchParams.get("excludeHeaderAttributes") === "true" && {
+          excludeHeaderAttributes: true
+        }),
         ...(isEmpty(classificationParams || glossaryTypeParams) && {
           entityFilters: !isEmpty(entityFilterParams)
             ? searchParamsAPiQuery(entityFilterParams)
@@ -1000,12 +1012,20 @@ const SearchResult = ({ classificationParams, 
glossaryTypeParams, hideFilters }:
 
     return hideColumns;
   };
+  const latestEntitiesSortBy = searchParams.get("sortBy");
+  const latestEntitiesSortOrder = searchParams.get("sortOrder");
   const getDefaultSort = useMemo(() => {
     if (isDslAggregate) {
       return [] as any[]; // no default sorting for DSL aggregates
     }
-    return [{ id: "name", asc: true }];
-  }, [isDslAggregate]);
+    if (
+      latestEntitiesSortBy === "__timestamp" &&
+      latestEntitiesSortOrder === "DESCENDING"
+    ) {
+      return [{ id: "__timestamp", desc: true }];
+    }
+    return [{ id: "name", desc: false }];
+  }, [isDslAggregate, latestEntitiesSortBy, latestEntitiesSortOrder]);
 
   return (
     <Stack position="relative" gap={"1rem"}>

Reply via email to