Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add extension point for workspace home page #21033

Merged
merged 13 commits into from
Aug 15, 2022
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ export type Extensions = Partial<{
'embedded.documentation.url': string;
'dashboard.nav.right': React.ComponentType;
'navbar.right': React.ComponentType;
'welcome.message': React.ComponentType;
'welcome.banner': React.ComponentType;
'welcome.main.replacement': React.ComponentType;
}>;

/**
Expand Down
49 changes: 30 additions & 19 deletions superset-frontend/src/components/ListViewCard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ const Cover = styled.div`
const TitleContainer = styled.div`
display: flex;
justify-content: flex-start;
flex-direction: row;
flex-direction: column;

.card-actions {
margin-left: auto;
Expand All @@ -82,6 +82,12 @@ const TitleContainer = styled.div`
align-items: center;
}
}

.titleRow {
display: flex;
justify-content: flex-start;
flex-direction: row;
}
`;

const TitleLink = styled.span`
Expand Down Expand Up @@ -141,6 +147,7 @@ const AnchorLink: React.FC<LinkProps> = ({ to, children }) => (

interface CardProps {
title?: React.ReactNode;
subtitle?: React.ReactNode;
url?: string;
linkComponent?: React.ComponentType<LinkProps>;
imgURL?: string;
Expand All @@ -161,6 +168,7 @@ interface CardProps {

function ListViewCard({
title,
subtitle,
url,
linkComponent,
titleRight,
Expand Down Expand Up @@ -245,24 +253,27 @@ function ListViewCard({
<AntdCard.Meta
title={
<TitleContainer>
<Tooltip title={title}>
<TitleLink>
<Link to={url!}>
{certifiedBy && (
<>
<CertifiedBadge
certifiedBy={certifiedBy}
details={certificationDetails}
/>{' '}
</>
)}
{title}
</Link>
</TitleLink>
</Tooltip>
{titleRight && <TitleRight>{titleRight}</TitleRight>}
<div className="card-actions" data-test="card-actions">
{actions}
{subtitle || null}
<div className="titleRow">
<Tooltip title={title}>
<TitleLink>
<Link to={url!}>
{certifiedBy && (
<>
<CertifiedBadge
certifiedBy={certifiedBy}
details={certificationDetails}
/>{' '}
</>
)}
{title}
</Link>
</TitleLink>
</Tooltip>
{titleRight && <TitleRight>{titleRight}</TitleRight>}
<div className="card-actions" data-test="card-actions">
{actions}
</div>
</div>
</TitleContainer>
}
Expand Down
1 change: 1 addition & 0 deletions superset-frontend/src/components/Radio/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,5 @@ const StyledGroup = styled(AntdRadio.Group)`

export const Radio = Object.assign(StyledRadio, {
Group: StyledGroup,
Button: AntdRadio.Button,
});
143 changes: 79 additions & 64 deletions superset-frontend/src/views/CRUD/welcome/Welcome.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,11 @@ function Welcome({ user, addDangerToast }: WelcomeProps) {
setItem(LocalStorageKeys.homepage_collapse_state, state);
};

const WelcomeMessageExtension = extensionsRegistry.get('welcome.message');
const WelcomeTopExtension = extensionsRegistry.get('welcome.banner');
const WelcomeMainExtension = extensionsRegistry.get(
'welcome.main.replacement',
);

useEffect(() => {
const activeTab = getItem(LocalStorageKeys.homepage_activity_filter, null);
Expand Down Expand Up @@ -282,71 +286,82 @@ function Welcome({ user, addDangerToast }: WelcomeProps) {
!activityData?.Examples && !activityData?.Viewed;
return (
<WelcomeContainer>
{WelcomeMessageExtension && <WelcomeMessageExtension />}
{WelcomeTopExtension && <WelcomeTopExtension />}
<WelcomeNav>
<h1 className="welcome-header">Home</h1>
{isFeatureEnabled(FeatureFlag.THUMBNAILS) ? (
<div className="switch">
<AntdSwitch checked={checked} onChange={handleToggle} />
<span>Thumbnails</span>
</div>
) : null}
</WelcomeNav>
<Collapse activeKey={activeState} onChange={handleCollapse} ghost bigger>
<Collapse.Panel header={t('Recents')} key="1">
{activityData &&
(activityData.Viewed ||
activityData.Examples ||
activityData.Created) &&
activeChild !== 'Loading' ? (
<ActivityTable
user={{ userId: user.userId! }} // user is definitely not a guest user on this page
activeChild={activeChild}
setActiveChild={setActiveChild}
activityData={activityData}
loadedCount={loadedCount}
/>
) : (
<LoadingCards />
)}
</Collapse.Panel>
<Collapse.Panel header={t('Dashboards')} key="2">
{!dashboardData || isRecentActivityLoading ? (
<LoadingCards cover={checked} />
) : (
<DashboardTable
user={user}
mine={dashboardData}
showThumbnails={checked}
examples={activityData?.Examples}
/>
)}
</Collapse.Panel>
<Collapse.Panel header={t('Charts')} key="3">
{!chartData || isRecentActivityLoading ? (
<LoadingCards cover={checked} />
) : (
<ChartTable
showThumbnails={checked}
user={user}
mine={chartData}
examples={activityData?.Examples}
/>
)}
</Collapse.Panel>
<Collapse.Panel header={t('Saved queries')} key="4">
{!queryData ? (
<LoadingCards cover={checked} />
) : (
<SavedQueries
showThumbnails={checked}
user={user}
mine={queryData}
featureFlag={isFeatureEnabled(FeatureFlag.THUMBNAILS)}
/>
)}
</Collapse.Panel>
</Collapse>
{WelcomeMainExtension && <WelcomeMainExtension />}
{(!WelcomeTopExtension || !WelcomeMainExtension) && (
<>
<WelcomeNav>
<h1 className="welcome-header">Home</h1>
{isFeatureEnabled(FeatureFlag.THUMBNAILS) ? (
<div className="switch">
<AntdSwitch checked={checked} onChange={handleToggle} />
<span>Thumbnails</span>
</div>
) : null}
</WelcomeNav>
<Collapse
activeKey={activeState}
onChange={handleCollapse}
ghost
bigger
>
<Collapse.Panel header={t('Recents')} key="1">
{activityData &&
(activityData.Viewed ||
activityData.Examples ||
activityData.Created) &&
activeChild !== 'Loading' ? (
<ActivityTable
user={{ userId: user.userId! }} // user is definitely not a guest user on this page
activeChild={activeChild}
setActiveChild={setActiveChild}
activityData={activityData}
loadedCount={loadedCount}
/>
) : (
<LoadingCards />
)}
</Collapse.Panel>
<Collapse.Panel header={t('Dashboards')} key="2">
{!dashboardData || isRecentActivityLoading ? (
<LoadingCards cover={checked} />
) : (
<DashboardTable
user={user}
mine={dashboardData}
showThumbnails={checked}
examples={activityData?.Examples}
/>
)}
</Collapse.Panel>
<Collapse.Panel header={t('Charts')} key="3">
{!chartData || isRecentActivityLoading ? (
<LoadingCards cover={checked} />
) : (
<ChartTable
showThumbnails={checked}
user={user}
mine={chartData}
examples={activityData?.Examples}
/>
)}
</Collapse.Panel>
<Collapse.Panel header={t('Saved queries')} key="4">
{!queryData ? (
<LoadingCards cover={checked} />
) : (
<SavedQueries
showThumbnails={checked}
user={user}
mine={queryData}
featureFlag={isFeatureEnabled(FeatureFlag.THUMBNAILS)}
/>
)}
</Collapse.Panel>
</Collapse>
</>
)}
</WelcomeContainer>
);
}
Expand Down
4 changes: 4 additions & 0 deletions superset/charts/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,16 +114,20 @@ def ensure_thumbnails_enabled(self) -> Optional[Response]:
"cache_timeout",
"certified_by",
"certification_details",
"changed_on_delta_humanized",
"dashboards.dashboard_title",
"dashboards.id",
"dashboards.json_metadata",
"description",
"id",
"owners.first_name",
"owners.id",
"owners.last_name",
"owners.username",
"params",
"slice_name",
"thumbnail_url",
"url",
"viz_type",
"query_context",
"is_managed_externally",
Expand Down
1 change: 1 addition & 0 deletions superset/queries/saved_queries/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ class SavedQueryRestApi(BaseSupersetModelRestApi):
base_filters = [["id", SavedQueryFilter, lambda: []]]

show_columns = [
"changed_on_delta_humanized",
"created_by.first_name",
"created_by.id",
"created_by.last_name",
Expand Down
15 changes: 14 additions & 1 deletion tests/integration_tests/charts/api_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
# isort:skip_file
"""Unit tests for Superset"""
import json
import logging
from io import BytesIO
from zipfile import is_zipfile, ZipFile

Expand Down Expand Up @@ -762,7 +763,19 @@ def test_get_chart(self):
"is_managed_externally": False,
}
data = json.loads(rv.data.decode("utf-8"))
self.assertEqual(data["result"], expected_result)
self.assertIn("changed_on_delta_humanized", data["result"])
self.assertIn("id", data["result"])
self.assertIn("thumbnail_url", data["result"])
self.assertIn("url", data["result"])
for key, value in data["result"].items():
# We can't assert timestamp values or id/urls
if key not in (
"changed_on_delta_humanized",
"id",
"thumbnail_url",
"url",
):
self.assertEqual(value, expected_result[key])
db.session.delete(chart)
db.session.commit()

Expand Down
4 changes: 3 additions & 1 deletion tests/integration_tests/queries/saved_queries/api_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -525,8 +525,10 @@ def test_get_saved_query(self):
"label": "label1",
}
data = json.loads(rv.data.decode("utf-8"))
self.assertIn("changed_on_delta_humanized", data["result"])
for key, value in data["result"].items():
assert value == expected_result[key]
if key not in ("changed_on_delta_humanized",):
assert value == expected_result[key]

def test_get_saved_query_not_found(self):
"""
Expand Down