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

justinpark pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git


The following commit(s) were added to refs/heads/master by this push:
     new 832e028b39 fix(welcome): perf on distinct recent activities (#32608)
832e028b39 is described below

commit 832e028b39ef6987b6c13ffeeb4f589c98514018
Author: JUST.in DO IT <[email protected]>
AuthorDate: Thu Mar 13 09:44:48 2025 -0700

    fix(welcome): perf on distinct recent activities (#32608)
---
 .../superset-ui-core/src/utils/lruCache.ts         |  4 ++
 .../superset-ui-core/test/utils/lruCache.test.ts   |  4 ++
 .../src/features/home/ActivityTable.tsx            | 14 +------
 superset-frontend/src/features/home/types.ts       | 12 ++++++
 superset-frontend/src/pages/Home/Home.test.tsx     | 44 +++++++++++++++++++++-
 superset-frontend/src/pages/Home/index.tsx         |  2 +-
 superset-frontend/src/views/CRUD/utils.tsx         |  9 ++++-
 7 files changed, 71 insertions(+), 18 deletions(-)

diff --git a/superset-frontend/packages/superset-ui-core/src/utils/lruCache.ts 
b/superset-frontend/packages/superset-ui-core/src/utils/lruCache.ts
index f6785850c2..e92005986a 100644
--- a/superset-frontend/packages/superset-ui-core/src/utils/lruCache.ts
+++ b/superset-frontend/packages/superset-ui-core/src/utils/lruCache.ts
@@ -67,6 +67,10 @@ class LRUCache<T> {
   public get size() {
     return this.cache.size;
   }
+
+  public values(): T[] {
+    return [...this.cache.values()];
+  }
 }
 
 export function lruCache<T>(capacity = 100) {
diff --git 
a/superset-frontend/packages/superset-ui-core/test/utils/lruCache.test.ts 
b/superset-frontend/packages/superset-ui-core/test/utils/lruCache.test.ts
index f8a077eba0..2c7f1fafa4 100644
--- a/superset-frontend/packages/superset-ui-core/test/utils/lruCache.test.ts
+++ b/superset-frontend/packages/superset-ui-core/test/utils/lruCache.test.ts
@@ -35,8 +35,11 @@ test('LRU operations', () => {
   expect(cache.size).toBe(3);
   expect(cache.has('1')).toBeFalsy();
   expect(cache.get('1')).toBeUndefined();
+  expect(cache.values()).toEqual(['b', 'c', 'd']);
   cache.get('2');
+  expect(cache.values()).toEqual(['c', 'd', 'b']);
   cache.set('5', 'e');
+  expect(cache.values()).toEqual(['d', 'b', 'e']);
   expect(cache.has('2')).toBeTruthy();
   expect(cache.has('3')).toBeFalsy();
   // @ts-expect-error
@@ -44,6 +47,7 @@ test('LRU operations', () => {
   // @ts-expect-error
   expect(() => cache.get(0)).toThrow(TypeError);
   expect(cache.size).toBe(3);
+  expect(cache.values()).toEqual(['d', 'b', 'e']);
   cache.clear();
   expect(cache.size).toBe(0);
   expect(cache.capacity).toBe(3);
diff --git a/superset-frontend/src/features/home/ActivityTable.tsx 
b/superset-frontend/src/features/home/ActivityTable.tsx
index dc456015ff..3eb35598ac 100644
--- a/superset-frontend/src/features/home/ActivityTable.tsx
+++ b/superset-frontend/src/features/home/ActivityTable.tsx
@@ -33,19 +33,7 @@ import { Chart } from 'src/types/Chart';
 import Icons from 'src/components/Icons';
 import SubMenu from './SubMenu';
 import EmptyState from './EmptyState';
-import { WelcomeTable } from './types';
-
-/**
- * Return result from /api/v1/log/recent_activity/
- */
-interface RecentActivity {
-  action: string;
-  item_type: 'slice' | 'dashboard';
-  item_url: string;
-  item_title: string;
-  time: number;
-  time_delta_humanized?: string;
-}
+import { WelcomeTable, RecentActivity } from './types';
 
 interface RecentSlice extends RecentActivity {
   item_type: 'slice';
diff --git a/superset-frontend/src/features/home/types.ts 
b/superset-frontend/src/features/home/types.ts
index 105cded78f..a59e9fcd85 100644
--- a/superset-frontend/src/features/home/types.ts
+++ b/superset-frontend/src/features/home/types.ts
@@ -55,3 +55,15 @@ export enum GlobalMenuDataOptions {
   ExcelUpload = 'excelUpload',
   ColumnarUpload = 'columnarUpload',
 }
+
+/**
+ * Return result from /api/v1/log/recent_activity/
+ */
+export interface RecentActivity {
+  action: string;
+  item_type: 'slice' | 'dashboard';
+  item_url: string;
+  item_title: string;
+  time: number;
+  time_delta_humanized?: string;
+}
diff --git a/superset-frontend/src/pages/Home/Home.test.tsx 
b/superset-frontend/src/pages/Home/Home.test.tsx
index ea0bc161cb..e9dd039cbd 100644
--- a/superset-frontend/src/pages/Home/Home.test.tsx
+++ b/superset-frontend/src/pages/Home/Home.test.tsx
@@ -65,9 +65,35 @@ fetchMock.get(savedQueryEndpoint, {
   result: [],
 });
 
+const mockRecentActivityResult = [
+  {
+    action: 'dashboard',
+    item_title: "World Bank's Data",
+    item_type: 'dashboard',
+    item_url: '/superset/dashboard/world_health/',
+    time: 1741644942130.566,
+    time_delta_humanized: 'a day ago',
+  },
+  {
+    action: 'dashboard',
+    item_title: '[ untitled dashboard ]',
+    item_type: 'dashboard',
+    item_url: '/superset/dashboard/19/',
+    time: 1741644881695.7869,
+    time_delta_humanized: 'a day ago',
+  },
+  {
+    action: 'dashboard',
+    item_title: '[ untitled dashboard ]',
+    item_type: 'dashboard',
+    item_url: '/superset/dashboard/19/',
+    time: 1741644381695.7869,
+    time_delta_humanized: 'two day ago',
+  },
+];
+
 fetchMock.get(recentActivityEndpoint, {
-  Created: [],
-  Viewed: [],
+  result: mockRecentActivityResult,
 });
 
 fetchMock.get(chartInfoEndpoint, {
@@ -149,6 +175,20 @@ test('With sql role - renders all panels on the page on 
page load', async () =>
   expect(panels).toHaveLength(4);
 });
 
+test('With sql role - renders distinct recent activities', async () => {
+  await renderWelcome();
+  const recentPanel = screen.getByRole('button', { name: 'right Recents' });
+  userEvent.click(recentPanel);
+  await waitFor(() =>
+    expect(
+      screen.queryAllByText(mockRecentActivityResult[0].item_title),
+    ).toHaveLength(1),
+  );
+  expect(
+    screen.queryAllByText(mockRecentActivityResult[1].item_title),
+  ).toHaveLength(1);
+});
+
 test('With sql role - calls api methods in parallel on page load', async () => 
{
   await renderWelcome();
   expect(fetchMock.calls(chartsEndpoint)).toHaveLength(2);
diff --git a/superset-frontend/src/pages/Home/index.tsx 
b/superset-frontend/src/pages/Home/index.tsx
index 90466fe96e..6d005c79f6 100644
--- a/superset-frontend/src/pages/Home/index.tsx
+++ b/superset-frontend/src/pages/Home/index.tsx
@@ -156,7 +156,7 @@ function Welcome({ user, addDangerToast }: WelcomeProps) {
   const canReadSavedQueries = userHasPermission(user, 'SavedQuery', 
'can_read');
   const userid = user.userId;
   const id = userid!.toString(); // confident that user is not a guest user
-  const params = rison.encode({ page_size: 6 });
+  const params = rison.encode({ page_size: 24, distinct: false });
   const recent = `/api/v1/log/recent_activity/?q=${params}`;
   const [activeChild, setActiveChild] = useState('Loading');
   const userKey = dangerouslyGetItemDoNotUse(id, null);
diff --git a/superset-frontend/src/views/CRUD/utils.tsx 
b/superset-frontend/src/views/CRUD/utils.tsx
index 8fd77373b5..86075e30a9 100644
--- a/superset-frontend/src/views/CRUD/utils.tsx
+++ b/superset-frontend/src/views/CRUD/utils.tsx
@@ -26,6 +26,7 @@ import {
   SupersetTheme,
   getClientErrorObject,
   t,
+  lruCache,
 } from '@superset-ui/core';
 import Chart from 'src/types/Chart';
 import { intersection } from 'lodash';
@@ -34,7 +35,7 @@ import { FetchDataConfig, FilterValue } from 
'src/components/ListView';
 import SupersetText from 'src/utils/textUtils';
 import { findPermission } from 'src/utils/findPermission';
 import { User } from 'src/types/bootstrapTypes';
-import { WelcomeTable } from 'src/features/home/types';
+import { RecentActivity, WelcomeTable } from 'src/features/home/types';
 import { Dashboard, Filter, TableTab } from './types';
 
 // Modifies the rison encoding slightly to match the backend's rison 
encoding/decoding. Applies globally.
@@ -223,10 +224,14 @@ export const getRecentActivityObjs = (
 ) =>
   SupersetClient.get({ endpoint: recent }).then(recentsRes => {
     const res: any = {};
+    const distinctRes = lruCache<RecentActivity>(6);
+    recentsRes.json.result.reverse().forEach((record: RecentActivity) => {
+      distinctRes.set(record.item_url, record);
+    });
     return getFilteredChartsandDashboards(addDangerToast, filters).then(
       ({ other }) => {
         res.other = other;
-        res.viewed = recentsRes.json.result;
+        res.viewed = distinctRes.values().reverse();
         return res;
       },
     );

Reply via email to