feat(homescreen and cards): Toggle thumbnails off or on and feature flag (#13683)
This commit is contained in:
parent
1d5c58d005
commit
3e4c3bddb9
|
|
@ -23,8 +23,10 @@ import thunk from 'redux-thunk';
|
|||
import fetchMock from 'fetch-mock';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import configureStore from 'redux-mock-store';
|
||||
import * as featureFlags from 'src/featureFlags';
|
||||
import Welcome from 'src/views/CRUD/welcome/Welcome';
|
||||
import { ReactWrapper } from 'enzyme';
|
||||
import waitForComponentToPaint from 'spec/helpers/waitForComponentToPaint';
|
||||
|
||||
const mockStore = configureStore([thunk]);
|
||||
const store = mockStore({});
|
||||
|
|
@ -92,19 +94,19 @@ fetchMock.get(savedQueryInfoEndpoint, {
|
|||
permissions: [],
|
||||
});
|
||||
|
||||
describe('Welcome', () => {
|
||||
const mockedProps = {
|
||||
user: {
|
||||
username: 'alpha',
|
||||
firstName: 'alpha',
|
||||
lastName: 'alpha',
|
||||
createdOn: '2016-11-11T12:34:17',
|
||||
userId: 5,
|
||||
email: 'alpha@alpha.com',
|
||||
isActive: true,
|
||||
},
|
||||
};
|
||||
const mockedProps = {
|
||||
user: {
|
||||
username: 'alpha',
|
||||
firstName: 'alpha',
|
||||
lastName: 'alpha',
|
||||
createdOn: '2016-11-11T12:34:17',
|
||||
userId: 5,
|
||||
email: 'alpha@alpha.com',
|
||||
isActive: true,
|
||||
},
|
||||
};
|
||||
|
||||
describe('Welcome', () => {
|
||||
let wrapper: ReactWrapper;
|
||||
|
||||
beforeAll(async () => {
|
||||
|
|
@ -136,3 +138,44 @@ describe('Welcome', () => {
|
|||
expect(dashboardCall).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
async function mountAndWait(props = mockedProps) {
|
||||
const wrapper = mount(
|
||||
<Provider store={store}>
|
||||
<Welcome {...props} />
|
||||
</Provider>,
|
||||
);
|
||||
await waitForComponentToPaint(wrapper);
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
describe('Welcome page with toggle switch', () => {
|
||||
let wrapper: ReactWrapper;
|
||||
let isFeatureEnabledMock: any;
|
||||
|
||||
beforeAll(async () => {
|
||||
isFeatureEnabledMock = jest
|
||||
.spyOn(featureFlags, 'isFeatureEnabled')
|
||||
.mockReturnValue(true);
|
||||
await act(async () => {
|
||||
wrapper = await mountAndWait();
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
isFeatureEnabledMock.restore();
|
||||
});
|
||||
|
||||
it('shows a toggle button when feature flags is turned on', async () => {
|
||||
await waitForComponentToPaint(wrapper);
|
||||
expect(wrapper.find('Switch')).toExist();
|
||||
});
|
||||
it('does not show thumbnails when switch is off', async () => {
|
||||
act(() => {
|
||||
// @ts-ignore
|
||||
wrapper.find('button[role="switch"]').props().onClick();
|
||||
});
|
||||
await waitForComponentToPaint(wrapper);
|
||||
expect(wrapper.find('ImageLoader')).not.toExist();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -44,6 +44,8 @@ interface ChartCardProps {
|
|||
favoriteStatus: boolean;
|
||||
chartFilter?: string;
|
||||
userId?: number;
|
||||
showThumbnails?: boolean;
|
||||
featureFlag?: boolean;
|
||||
}
|
||||
|
||||
export default function ChartCard({
|
||||
|
|
@ -55,10 +57,12 @@ export default function ChartCard({
|
|||
addSuccessToast,
|
||||
refreshData,
|
||||
loading,
|
||||
showThumbnails,
|
||||
saveFavoriteStatus,
|
||||
favoriteStatus,
|
||||
chartFilter,
|
||||
userId,
|
||||
featureFlag,
|
||||
}: ChartCardProps) {
|
||||
const canEdit = hasPerm('can_write');
|
||||
const canDelete = hasPerm('can_write');
|
||||
|
|
@ -138,6 +142,7 @@ export default function ChartCard({
|
|||
<ListViewCard
|
||||
loading={loading}
|
||||
title={chart.slice_name}
|
||||
cover={!featureFlag || !showThumbnails ? <></> : null}
|
||||
url={bulkSelectEnabled ? undefined : chart.url}
|
||||
imgURL={chart.thumbnail_url || ''}
|
||||
imgFallbackURL="/static/assets/images/chart-card-fallback.svg"
|
||||
|
|
|
|||
|
|
@ -46,6 +46,8 @@ interface DashboardCardProps {
|
|||
favoriteStatus: boolean;
|
||||
dashboardFilter?: string;
|
||||
userId?: number;
|
||||
showThumbnails?: boolean;
|
||||
featureFlag?: boolean;
|
||||
}
|
||||
|
||||
function DashboardCard({
|
||||
|
|
@ -60,6 +62,8 @@ function DashboardCard({
|
|||
openDashboardEditModal,
|
||||
favoriteStatus,
|
||||
saveFavoriteStatus,
|
||||
showThumbnails,
|
||||
featureFlag,
|
||||
}: DashboardCardProps) {
|
||||
const canEdit = hasPerm('can_write');
|
||||
const canDelete = hasPerm('can_write');
|
||||
|
|
@ -146,6 +150,7 @@ function DashboardCard({
|
|||
titleRight={
|
||||
<Label>{dashboard.published ? t('published') : t('draft')}</Label>
|
||||
}
|
||||
cover={!featureFlag || !showThumbnails ? <></> : null}
|
||||
url={bulkSelectEnabled ? undefined : dashboard.url}
|
||||
imgURL={dashboard.thumbnail_url}
|
||||
imgFallbackURL="/static/assets/images/dashboard-card-fallback.svg"
|
||||
|
|
|
|||
|
|
@ -35,6 +35,8 @@ export interface DashboardTableProps {
|
|||
search: string;
|
||||
user?: User;
|
||||
mine: Array<Dashboard>;
|
||||
showThumbnails?: boolean;
|
||||
featureFlag?: boolean;
|
||||
}
|
||||
|
||||
export interface Dashboard {
|
||||
|
|
|
|||
|
|
@ -44,6 +44,8 @@ interface ChartTableProps {
|
|||
chartFilter?: string;
|
||||
user?: User;
|
||||
mine: Array<any>;
|
||||
showThumbnails: boolean;
|
||||
featureFlag: boolean;
|
||||
}
|
||||
|
||||
function ChartTable({
|
||||
|
|
@ -51,6 +53,8 @@ function ChartTable({
|
|||
addDangerToast,
|
||||
addSuccessToast,
|
||||
mine,
|
||||
showThumbnails,
|
||||
featureFlag,
|
||||
}: ChartTableProps) {
|
||||
const history = useHistory();
|
||||
const {
|
||||
|
|
@ -180,7 +184,9 @@ function ChartTable({
|
|||
chart={e}
|
||||
userId={user?.userId}
|
||||
hasPerm={hasPerm}
|
||||
showThumbnails={showThumbnails}
|
||||
bulkSelectEnabled={bulkSelectEnabled}
|
||||
featureFlag={featureFlag}
|
||||
refreshData={refreshData}
|
||||
addDangerToast={addDangerToast}
|
||||
addSuccessToast={addSuccessToast}
|
||||
|
|
|
|||
|
|
@ -42,6 +42,8 @@ function DashboardTable({
|
|||
addDangerToast,
|
||||
addSuccessToast,
|
||||
mine,
|
||||
showThumbnails,
|
||||
featureFlag,
|
||||
}: DashboardTableProps) {
|
||||
const history = useHistory();
|
||||
const {
|
||||
|
|
@ -191,6 +193,8 @@ function DashboardTable({
|
|||
dashboard={e}
|
||||
hasPerm={hasPerm}
|
||||
bulkSelectEnabled={false}
|
||||
featureFlag={featureFlag}
|
||||
showThumbnails={showThumbnails}
|
||||
dashboardFilter={dashboardFilter}
|
||||
refreshData={refreshData}
|
||||
addDangerToast={addDangerToast}
|
||||
|
|
|
|||
|
|
@ -58,6 +58,8 @@ interface SavedQueriesProps {
|
|||
addDangerToast: (arg0: string) => void;
|
||||
addSuccessToast: (arg0: string) => void;
|
||||
mine: Array<Query>;
|
||||
showThumbnails: boolean;
|
||||
featureFlag: boolean;
|
||||
}
|
||||
|
||||
export const CardStyles = styled.div`
|
||||
|
|
@ -110,6 +112,8 @@ const SavedQueries = ({
|
|||
addDangerToast,
|
||||
addSuccessToast,
|
||||
mine,
|
||||
showThumbnails,
|
||||
featureFlag,
|
||||
}: SavedQueriesProps) => {
|
||||
const {
|
||||
state: { loading, resourceCollection: queries },
|
||||
|
|
@ -307,7 +311,7 @@ const SavedQueries = ({
|
|||
imgFallbackURL="/static/assets/images/empty-query.svg"
|
||||
description={t('Last run %s', q.changed_on_delta_humanized)}
|
||||
cover={
|
||||
q?.sql?.length ? (
|
||||
q?.sql?.length && showThumbnails && featureFlag ? (
|
||||
<QueryContainer>
|
||||
<SyntaxHighlighter
|
||||
language="sql"
|
||||
|
|
@ -328,8 +332,10 @@ const SavedQueries = ({
|
|||
{shortenSQL(q.sql, 25)}
|
||||
</SyntaxHighlighter>
|
||||
</QueryContainer>
|
||||
) : (
|
||||
) : showThumbnails && !q?.sql?.length ? (
|
||||
false
|
||||
) : (
|
||||
<></>
|
||||
)
|
||||
}
|
||||
actions={
|
||||
|
|
|
|||
|
|
@ -21,6 +21,10 @@ import { styled, t } from '@superset-ui/core';
|
|||
import Collapse from 'src/common/components/Collapse';
|
||||
import { User } from 'src/types/bootstrapTypes';
|
||||
import { reject } from 'lodash';
|
||||
import {
|
||||
getFromLocalStorage,
|
||||
setInLocalStorage,
|
||||
} from 'src/utils/localStorageHelpers';
|
||||
import withToasts from 'src/messageToasts/enhancers/withToasts';
|
||||
import Loading from 'src/components/Loading';
|
||||
import {
|
||||
|
|
@ -29,6 +33,8 @@ import {
|
|||
mq,
|
||||
getUserOwnedObjects,
|
||||
} from 'src/views/CRUD/utils';
|
||||
import { FeatureFlag, isFeatureEnabled } from 'src/featureFlags';
|
||||
import { Switch } from 'src/common/components';
|
||||
|
||||
import ActivityTable from './ActivityTable';
|
||||
import ChartTable from './ChartTable';
|
||||
|
|
@ -83,9 +89,31 @@ const WelcomeContainer = styled.div`
|
|||
}
|
||||
`;
|
||||
|
||||
const WelcomeNav = styled.div`
|
||||
height: 50px;
|
||||
background-color: white;
|
||||
margin-top: ${({ theme }) => theme.gridUnit * -4 - 1}px;
|
||||
.navbar-brand {
|
||||
margin-left: ${({ theme }) => theme.gridUnit * 2}px;
|
||||
font-weight: ${({ theme }) => theme.typography.weights.bold};
|
||||
}
|
||||
.switch {
|
||||
float: right;
|
||||
margin: ${({ theme }) => theme.gridUnit * 5}px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
span {
|
||||
display: block;
|
||||
margin: ${({ theme }) => theme.gridUnit * 1}px;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
function Welcome({ user, addDangerToast }: WelcomeProps) {
|
||||
const recent = `/superset/recent_activity/${user.userId}/?limit=6`;
|
||||
const [activeChild, setActiveChild] = useState('Viewed');
|
||||
const [checked, setChecked] = useState(true);
|
||||
const [activityData, setActivityData] = useState<ActivityData | null>(null);
|
||||
const [chartData, setChartData] = useState<Array<object> | null>(null);
|
||||
const [queryData, setQueryData] = useState<Array<object> | null>(null);
|
||||
|
|
@ -93,7 +121,12 @@ function Welcome({ user, addDangerToast }: WelcomeProps) {
|
|||
null,
|
||||
);
|
||||
|
||||
const userid = user.userId;
|
||||
const id = userid.toString();
|
||||
|
||||
useEffect(() => {
|
||||
const userKey = getFromLocalStorage(id, null);
|
||||
if (userKey && !userKey.thumbnails) setChecked(false);
|
||||
getRecentAcitivtyObjs(user.userId, recent, addDangerToast)
|
||||
.then(res => {
|
||||
const data: ActivityData | null = {};
|
||||
|
|
@ -117,7 +150,6 @@ function Welcome({ user, addDangerToast }: WelcomeProps) {
|
|||
);
|
||||
|
||||
// Sets other activity data in parallel with recents api call
|
||||
const id = user.userId;
|
||||
getUserOwnedObjects(id, 'dashboard')
|
||||
.then(r => {
|
||||
setDashboardData(r);
|
||||
|
|
@ -148,6 +180,11 @@ function Welcome({ user, addDangerToast }: WelcomeProps) {
|
|||
});
|
||||
}, []);
|
||||
|
||||
const handleToggle = () => {
|
||||
setChecked(!checked);
|
||||
setInLocalStorage(id, { thumbnails: !checked });
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setActivityData(activityData => ({
|
||||
...activityData,
|
||||
|
|
@ -161,6 +198,15 @@ function Welcome({ user, addDangerToast }: WelcomeProps) {
|
|||
|
||||
return (
|
||||
<WelcomeContainer>
|
||||
<WelcomeNav>
|
||||
<span className="navbar-brand">Home</span>
|
||||
{isFeatureEnabled(FeatureFlag.THUMBNAILS) ? (
|
||||
<div className="switch">
|
||||
<Switch checked={checked} onChange={handleToggle} />
|
||||
<span>Thumbnails</span>
|
||||
</div>
|
||||
) : null}
|
||||
</WelcomeNav>
|
||||
<Collapse defaultActiveKey={['1', '2', '3', '4']} ghost bigger>
|
||||
<Collapse.Panel header={t('Recents')} key="1">
|
||||
{activityData && (activityData.Viewed || activityData.Examples) ? (
|
||||
|
|
@ -178,21 +224,36 @@ function Welcome({ user, addDangerToast }: WelcomeProps) {
|
|||
{!dashboardData ? (
|
||||
<Loading position="inline" />
|
||||
) : (
|
||||
<DashboardTable user={user} mine={dashboardData} />
|
||||
<DashboardTable
|
||||
user={user}
|
||||
mine={dashboardData}
|
||||
showThumbnails={checked}
|
||||
featureFlag={isFeatureEnabled(FeatureFlag.THUMBNAILS)}
|
||||
/>
|
||||
)}
|
||||
</Collapse.Panel>
|
||||
<Collapse.Panel header={t('Saved queries')} key="3">
|
||||
{!queryData ? (
|
||||
<Loading position="inline" />
|
||||
) : (
|
||||
<SavedQueries user={user} mine={queryData} />
|
||||
<SavedQueries
|
||||
showThumbnails={checked}
|
||||
user={user}
|
||||
mine={queryData}
|
||||
featureFlag={isFeatureEnabled(FeatureFlag.THUMBNAILS)}
|
||||
/>
|
||||
)}
|
||||
</Collapse.Panel>
|
||||
<Collapse.Panel header={t('Charts')} key="4">
|
||||
{!chartData ? (
|
||||
<Loading position="inline" />
|
||||
) : (
|
||||
<ChartTable user={user} mine={chartData} />
|
||||
<ChartTable
|
||||
showThumbnails={checked}
|
||||
user={user}
|
||||
mine={chartData}
|
||||
featureFlag={isFeatureEnabled(FeatureFlag.THUMBNAILS)}
|
||||
/>
|
||||
)}
|
||||
</Collapse.Panel>
|
||||
</Collapse>
|
||||
|
|
|
|||
Loading…
Reference in New Issue