Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,19 @@ 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
expect(() => cache.set(0)).toThrow(TypeError);
// @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);
Expand Down
14 changes: 1 addition & 13 deletions superset-frontend/src/features/home/ActivityTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
12 changes: 12 additions & 0 deletions superset-frontend/src/features/home/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
44 changes: 42 additions & 2 deletions superset-frontend/src/pages/Home/Home.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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, {
Expand Down Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion superset-frontend/src/pages/Home/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
9 changes: 7 additions & 2 deletions superset-frontend/src/views/CRUD/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
SupersetTheme,
getClientErrorObject,
t,
lruCache,
} from '@superset-ui/core';
import Chart from 'src/types/Chart';
import { intersection } from 'lodash';
Expand All @@ -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.
Expand Down Expand Up @@ -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);
});
Comment on lines +228 to +230
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Incorrect Chronological Order in Recent Activities category Functionality

Tell me more
What is the issue?

The original array is being reversed before being processed, which modifies the chronological order of activities before applying the LRU cache.

Why this matters

This can lead to incorrect chronological ordering of recent activities in the UI, as older activities might appear as more recent than they actually are.

Suggested change ∙ Feature Preview

Process the records in their original order to maintain correct chronological sequence:

recentsRes.json.result.forEach((record: RecentActivity) => {
  distinctRes.set(record.item_url, record);
});

Report a problem with this comment

💬 Looking for more details? Reply to this comment to chat with Korbit.

return getFilteredChartsandDashboards(addDangerToast, filters).then(
({ other }) => {
res.other = other;
res.viewed = recentsRes.json.result;
res.viewed = distinctRes.values().reverse();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it necessary to reverse twice or can this be optimized?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The API returns the latest accesses in reverse chronological order. (i.e. [2025-03-10, 2025-03-09, 2025-03-07, ...]) For an LRU cache, access records need to be stored in chronological order. Therefore, the API list must be reversed to maintain a record of accesses in chronological order. After that, since the final LRU cache list has the most recent entries placed at the end, it needs to be reversed again to position the latest entries at the front.
This two-step reversal process is an algorithmically correct approach.

return res;
},
);
Expand Down
Loading