You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by di...@apache.org on 2022/08/22 13:17:56 UTC
[superset] 07/36: feat: add extension point for workspace home page (#21033)
This is an automated email from the ASF dual-hosted git repository.
diegopucci pushed a commit to branch chore/drill-to-detail-modal-tests
in repository https://gitbox.apache.org/repos/asf/superset.git
commit 3c793b291294274b9f1ad1d48053062212c16a28
Author: Moriah Kreeger <mo...@gmail.com>
AuthorDate: Mon Aug 15 12:16:40 2022 -0700
feat: add extension point for workspace home page (#21033)
* updates to allow insertion of workspace home sidescroll/table UI
* fix types
* fix User type import
* add welcome message to ui registry
* add extra fields to individual chart/query GET results (for workspace home required info)
* update list view card to support a subtitle
* add id to individual chart fetch
* update chart api test
* another test fix
* fix saved query test
* update extension types + insert point
* fix typing
* fix type name
---
.../src/ui-overrides/ExtensionsRegistry.ts | 2 +
.../src/components/ListViewCard/index.tsx | 49 ++++---
superset-frontend/src/components/Radio/index.tsx | 1 +
.../src/views/CRUD/welcome/Welcome.tsx | 143 ++++++++++++---------
superset/charts/api.py | 4 +
superset/queries/saved_queries/api.py | 1 +
tests/integration_tests/charts/api_tests.py | 15 ++-
.../queries/saved_queries/api_tests.py | 4 +-
8 files changed, 134 insertions(+), 85 deletions(-)
diff --git a/superset-frontend/packages/superset-ui-core/src/ui-overrides/ExtensionsRegistry.ts b/superset-frontend/packages/superset-ui-core/src/ui-overrides/ExtensionsRegistry.ts
index bf050a2c4c..82e68efcf0 100644
--- a/superset-frontend/packages/superset-ui-core/src/ui-overrides/ExtensionsRegistry.ts
+++ b/superset-frontend/packages/superset-ui-core/src/ui-overrides/ExtensionsRegistry.ts
@@ -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;
}>;
/**
diff --git a/superset-frontend/src/components/ListViewCard/index.tsx b/superset-frontend/src/components/ListViewCard/index.tsx
index 3078db8ea4..ac38a06ad9 100644
--- a/superset-frontend/src/components/ListViewCard/index.tsx
+++ b/superset-frontend/src/components/ListViewCard/index.tsx
@@ -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;
@@ -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`
@@ -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;
@@ -161,6 +168,7 @@ interface CardProps {
function ListViewCard({
title,
+ subtitle,
url,
linkComponent,
titleRight,
@@ -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>
}
diff --git a/superset-frontend/src/components/Radio/index.tsx b/superset-frontend/src/components/Radio/index.tsx
index 9ab656e4aa..f06392d278 100644
--- a/superset-frontend/src/components/Radio/index.tsx
+++ b/superset-frontend/src/components/Radio/index.tsx
@@ -57,4 +57,5 @@ const StyledGroup = styled(AntdRadio.Group)`
export const Radio = Object.assign(StyledRadio, {
Group: StyledGroup,
+ Button: AntdRadio.Button,
});
diff --git a/superset-frontend/src/views/CRUD/welcome/Welcome.tsx b/superset-frontend/src/views/CRUD/welcome/Welcome.tsx
index 8f403b999b..ddbd0b26d3 100644
--- a/superset-frontend/src/views/CRUD/welcome/Welcome.tsx
+++ b/superset-frontend/src/views/CRUD/welcome/Welcome.tsx
@@ -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);
@@ -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>
);
}
diff --git a/superset/charts/api.py b/superset/charts/api.py
index e7e511d4fd..09cc7352bf 100644
--- a/superset/charts/api.py
+++ b/superset/charts/api.py
@@ -114,16 +114,20 @@ class ChartRestApi(BaseSupersetModelRestApi):
"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",
diff --git a/superset/queries/saved_queries/api.py b/superset/queries/saved_queries/api.py
index a82a3dd8ef..1f2088d759 100644
--- a/superset/queries/saved_queries/api.py
+++ b/superset/queries/saved_queries/api.py
@@ -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",
diff --git a/tests/integration_tests/charts/api_tests.py b/tests/integration_tests/charts/api_tests.py
index aabe0f719a..c3546f32b9 100644
--- a/tests/integration_tests/charts/api_tests.py
+++ b/tests/integration_tests/charts/api_tests.py
@@ -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
@@ -762,7 +763,19 @@ class TestChartApi(SupersetTestCase, ApiOwnersTestCaseMixin, InsertChartMixin):
"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()
diff --git a/tests/integration_tests/queries/saved_queries/api_tests.py b/tests/integration_tests/queries/saved_queries/api_tests.py
index 2659bc224f..2569e7af40 100644
--- a/tests/integration_tests/queries/saved_queries/api_tests.py
+++ b/tests/integration_tests/queries/saved_queries/api_tests.py
@@ -525,8 +525,10 @@ class TestSavedQueryApi(SupersetTestCase):
"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):
"""