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;
},
);