Skip to content

Commit

Permalink
chore(homepage): separate out api calls to make homepage load more dy…
Browse files Browse the repository at this point in the history
…namically (#13500)

* separate out api calls

* add new loading states

* remove consoles

* update tests

* fix types and lint

* make code more robust and add test

* address comments

* address comments

* fix lint
  • Loading branch information
pkdotson authored Mar 22, 2021
1 parent 54b2bda commit bbc306c
Show file tree
Hide file tree
Showing 10 changed files with 277 additions and 156 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
*/
import React from 'react';
import { styledMount as mount } from 'spec/helpers/theming';
import { act } from 'react-dom/test-utils';
import { ReactWrapper } from 'enzyme';
import { Provider } from 'react-redux';
import fetchMock from 'fetch-mock';
import thunk from 'redux-thunk';
import waitForComponentToPaint from 'spec/helpers/waitForComponentToPaint';
import configureStore from 'redux-mock-store';
Expand All @@ -26,6 +30,9 @@ import ActivityTable from 'src/views/CRUD/welcome/ActivityTable';
const mockStore = configureStore([thunk]);
const store = mockStore({});

const chartsEndpoint = 'glob:*/api/v1/chart/?*';
const dashboardsEndpoint = 'glob:*/api/v1/dashboard/?*';

const mockData = {
Viewed: [
{
Expand All @@ -36,38 +43,58 @@ const mockData = {
table: {},
},
],
Edited: [
Created: [
{
dashboard_title: 'Dashboard_Test',
changed_on_utc: '24 Feb 2014 10:13:14',
url: '/fakeUrl/dashboard',
id: '3',
},
],
Created: [
};

fetchMock.get(chartsEndpoint, {
result: [
{
slice_name: 'ChartyChart',
changed_on_utc: '24 Feb 2014 10:13:14',
url: '/fakeUrl/explore',
id: '4',
table: {},
},
],
});

fetchMock.get(dashboardsEndpoint, {
result: [
{
dashboard_title: 'Dashboard_Test',
changed_on_utc: '24 Feb 2014 10:13:14',
url: '/fakeUrl/dashboard',
id: '3',
},
],
};
});

describe('ActivityTable', () => {
const activityProps = {
activeChild: 'Edited',
activeChild: 'Created',
activityData: mockData,
setActiveChild: jest.fn(),
user: { userId: '1' },
loading: false,
};
const wrapper = mount(<ActivityTable {...activityProps} />, {
context: { store },
});

let wrapper: ReactWrapper;

beforeAll(async () => {
await waitForComponentToPaint(wrapper);
await act(async () => {
wrapper = mount(
<Provider store={store}>
<ActivityTable {...activityProps} />
</Provider>,
);
});
});

it('the component renders', () => {
Expand All @@ -79,4 +106,32 @@ describe('ActivityTable', () => {
it('renders ActivityCards', async () => {
expect(wrapper.find('ListViewCard')).toExist();
});
it('calls the getEdited batch call when edited tab is clicked', async () => {
act(() => {
const handler = wrapper.find('li.no-router a').at(1).prop('onClick');
if (handler) {
handler({} as any);
}
});
await waitForComponentToPaint(wrapper);
const dashboardCall = fetchMock.calls(/dashboard\/\?q/);
const chartCall = fetchMock.calls(/chart\/\?q/);
expect(chartCall).toHaveLength(1);
expect(dashboardCall).toHaveLength(1);
});
it('show empty state if there is data', () => {
const activityProps = {
activeChild: 'Created',
activityData: {},
setActiveChild: jest.fn(),
user: { userId: '1' },
loading: false,
};
const wrapper = mount(
<Provider store={store}>
<ActivityTable {...activityProps} />
</Provider>,
);
expect(wrapper.find('EmptyState')).toExist();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,10 @@ fetchMock.get(savedQueryEndpoint, {
result: [],
});

fetchMock.get(recentActivityEndpoint, {});
fetchMock.get(recentActivityEndpoint, {
Created: [],
Viewed: [],
});

fetchMock.get(chartInfoEndpoint, {
permissions: [],
Expand Down Expand Up @@ -122,10 +125,14 @@ describe('Welcome', () => {
expect(wrapper.find('CollapsePanel')).toHaveLength(8);
});

it('calls batch method on page load', () => {
it('calls api methods in parallel on page load', () => {
const chartCall = fetchMock.calls(/chart\/\?q/);
const savedQueryCall = fetchMock.calls(/saved_query\/\?q/);
const recentCall = fetchMock.calls(/superset\/recent_activity\/*/);
const dashboardCall = fetchMock.calls(/dashboard\/\?q/);
expect(chartCall).toHaveLength(2);
expect(dashboardCall).toHaveLength(2);
expect(chartCall).toHaveLength(1);
expect(recentCall).toHaveLength(1);
expect(savedQueryCall).toHaveLength(1);
expect(dashboardCall).toHaveLength(1);
});
});
3 changes: 2 additions & 1 deletion superset-frontend/src/views/CRUD/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,12 @@ export function useListViewResource<D extends object = any>(
infoEnable = true,
defaultCollectionValue: D[] = [],
baseFilters?: FilterValue[], // must be memoized
initialLoadingState = true,
) {
const [state, setState] = useState<ListViewResourceState<D>>({
count: 0,
collection: defaultCollectionValue,
loading: true,
loading: initialLoadingState,
lastFetchDataConfig: null,
permissions: [],
bulkSelectEnabled: false,
Expand Down
6 changes: 6 additions & 0 deletions superset-frontend/src/views/CRUD/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ export type FavoriteStatus = {
[id: number]: boolean;
};

export type Filters = {
col: string;
opr: string;
value: string;
};

export interface DashboardTableProps {
addDangerToast: (message: string) => void;
addSuccessToast: (message: string) => void;
Expand Down
145 changes: 69 additions & 76 deletions superset-frontend/src/views/CRUD/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import Chart from 'src/types/Chart';
import rison from 'rison';
import { getClientErrorObject } from 'src/utils/getClientErrorObject';
import { FetchDataConfig } from 'src/components/ListView';
import { Dashboard } from './types';
import { Dashboard, Filters } from './types';

const createFetchResourceMethod = (method: string) => (
resource: string,
Expand Down Expand Up @@ -61,102 +61,95 @@ const createFetchResourceMethod = (method: string) => (
return [];
};

export const getRecentAcitivtyObjs = (
userId: string | number,
recent: string,
addDangerToast: (arg1: string, arg2: any) => any,
) => {
const getParams = (filters?: Array<any>) => {
const params = {
order_column: 'changed_on_delta_humanized',
order_direction: 'desc',
page: 0,
page_size: 3,
filters,
};
if (!filters) delete params.filters;
return rison.encode(params);
const getParams = (filters?: Array<Filters>) => {
const params = {
order_column: 'changed_on_delta_humanized',
order_direction: 'desc',
page: 0,
page_size: 3,
filters,
};
if (!filters) delete params.filters;
return rison.encode(params);
};

export const getEditedObjects = (userId: string | number) => {
const filters = {
// chart and dashbaord uses same filters
// for edited and created
edited: [
{
col: 'changed_by',
opr: 'rel_o_m',
value: `${userId}`,
},
],
created: [
{
col: 'created_by',
opr: 'rel_o_m',
value: `${userId}`,
},
],
};
const baseBatch = [
SupersetClient.get({ endpoint: recent }),
const batch = [
SupersetClient.get({
endpoint: `/api/v1/dashboard/?q=${getParams(filters.edited)}`,
}),
SupersetClient.get({
endpoint: `/api/v1/chart/?q=${getParams(filters.edited)}`,
}),
SupersetClient.get({
endpoint: `/api/v1/dashboard/?q=${getParams(filters.created)}`,
}),
SupersetClient.get({
endpoint: `/api/v1/chart/?q=${getParams(filters.created)}`,
}),
SupersetClient.get({
endpoint: `/api/v1/saved_query/?q=${getParams(filters.created)}`,
}),
];
return Promise.all(baseBatch).then(
([
recentsRes,
editedDash,
editedChart,
createdByDash,
createdByChart,
createdByQuery,
]) => {
const res: any = {
editedDash: editedDash.json?.result.slice(0, 3),
editedChart: editedChart.json?.result.slice(0, 3),
createdByDash: createdByDash.json?.result.slice(0, 3),
createdByChart: createdByChart.json?.result.slice(0, 3),
createdByQuery: createdByQuery.json?.result.slice(0, 3),
return Promise.all(batch)
.then(([editedCharts, editedDashboards]) => {
const res = {
editedDash: editedDashboards.json?.result.slice(0, 3),
editedChart: editedCharts.json?.result.slice(0, 3),
};
if (recentsRes.json.length === 0) {
const newBatch = [
SupersetClient.get({ endpoint: `/api/v1/chart/?q=${getParams()}` }),
SupersetClient.get({
endpoint: `/api/v1/dashboard/?q=${getParams()}`,
}),
];
return Promise.all(newBatch)
.then(([chartRes, dashboardRes]) => {
res.examples = [
...chartRes.json.result,
...dashboardRes.json.result,
];
return res;
})
.catch(errMsg =>
addDangerToast(
t('There was an error fetching your recent activity:'),
errMsg,
),
);
}
res.viewed = recentsRes.json;
return res;
},
);
})
.catch(err => err);
};

export const getUserOwnedObjects = (
userId: string | number,
resource: string,
) => {
const filters = {
created: [
{
col: 'created_by',
opr: 'rel_o_m',
value: `${userId}`,
},
],
};
return SupersetClient.get({
endpoint: `/api/v1/${resource}/?q=${getParams(filters.created)}`,
}).then(res => res.json?.result);
};

export const getRecentAcitivtyObjs = (
userId: string | number,
recent: string,
addDangerToast: (arg1: string, arg2: any) => any,
) =>
SupersetClient.get({ endpoint: recent }).then(recentsRes => {
const res: any = {};
if (recentsRes.json.length === 0) {
const newBatch = [
SupersetClient.get({ endpoint: `/api/v1/chart/?q=${getParams()}` }),
SupersetClient.get({
endpoint: `/api/v1/dashboard/?q=${getParams()}`,
}),
];
return Promise.all(newBatch)
.then(([chartRes, dashboardRes]) => {
res.examples = [...chartRes.json.result, ...dashboardRes.json.result];
return res;
})
.catch(errMsg =>
addDangerToast(
t('There was an error fetching your recent activity:'),
errMsg,
),
);
}
res.viewed = recentsRes.json;
return res;
});

export const createFetchRelated = createFetchResourceMethod('related');
export const createFetchDistinct = createFetchResourceMethod('distinct');

Expand Down
Loading

0 comments on commit bbc306c

Please sign in to comment.