mirror of
https://github.com/apache/superset.git
synced 2026-05-01 14:04:21 +00:00
Compare commits
7 Commits
docs/testi
...
geido/feat
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
abfad960e8 | ||
|
|
a0a21315ea | ||
|
|
3bdacded28 | ||
|
|
670ef0ea4f | ||
|
|
3cb3c8b235 | ||
|
|
0f4d054532 | ||
|
|
3593c6d9b0 |
@@ -30,13 +30,15 @@ import {
|
||||
import { css, SupersetTheme, t } from '@superset-ui/core';
|
||||
import { Tooltip } from 'src/components/Tooltip';
|
||||
import { useResizeDetector } from 'react-resize-detector';
|
||||
import { Skeleton } from 'src/components';
|
||||
|
||||
export type DynamicEditableTitleProps = {
|
||||
title: string;
|
||||
placeholder: string;
|
||||
title?: string;
|
||||
placeholder?: string;
|
||||
onSave: (title: string) => void;
|
||||
canEdit: boolean;
|
||||
label: string | undefined;
|
||||
label?: string | undefined;
|
||||
loading?: boolean;
|
||||
};
|
||||
|
||||
const titleStyles = (theme: SupersetTheme) => css`
|
||||
@@ -84,6 +86,7 @@ export const DynamicEditableTitle = memo(
|
||||
onSave,
|
||||
canEdit,
|
||||
label,
|
||||
loading,
|
||||
}: DynamicEditableTitleProps) => {
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const [currentTitle, setCurrentTitle] = useState(title || '');
|
||||
@@ -96,7 +99,7 @@ export const DynamicEditableTitle = memo(
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentTitle(title);
|
||||
setCurrentTitle(title ?? '');
|
||||
}, [title]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -173,6 +176,17 @@ export const DynamicEditableTitle = memo(
|
||||
[canEdit],
|
||||
);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<Skeleton.Button
|
||||
active
|
||||
css={css`
|
||||
min-width: 300px;
|
||||
`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div css={titleStyles} ref={containerRef}>
|
||||
<Tooltip
|
||||
|
||||
@@ -24,7 +24,7 @@ import { Tooltip } from 'src/components/Tooltip';
|
||||
import Icons from 'src/components/Icons';
|
||||
|
||||
export interface FaveStarProps {
|
||||
itemId: number;
|
||||
itemId?: number;
|
||||
isStarred?: boolean;
|
||||
showTooltip?: boolean;
|
||||
saveFaveStar(id: number, isStarred: boolean): any;
|
||||
@@ -41,19 +41,23 @@ const StyledLink = styled.a`
|
||||
|
||||
const FaveStar = ({
|
||||
itemId,
|
||||
isStarred,
|
||||
isStarred = false,
|
||||
showTooltip,
|
||||
saveFaveStar,
|
||||
fetchFaveStar,
|
||||
}: FaveStarProps) => {
|
||||
useEffect(() => {
|
||||
fetchFaveStar?.(itemId);
|
||||
if (itemId) {
|
||||
fetchFaveStar?.(itemId);
|
||||
}
|
||||
}, [fetchFaveStar, itemId]);
|
||||
|
||||
const onClick = useCallback(
|
||||
(e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
saveFaveStar(itemId, !!isStarred);
|
||||
if (itemId) {
|
||||
e.preventDefault();
|
||||
saveFaveStar(itemId, !!isStarred);
|
||||
}
|
||||
},
|
||||
[isStarred, itemId, saveFaveStar],
|
||||
);
|
||||
|
||||
@@ -23,6 +23,7 @@ import { styled } from '@superset-ui/core';
|
||||
import { Tooltip, TooltipPlacement } from 'src/components/Tooltip';
|
||||
import { ContentType } from './ContentType';
|
||||
import { config } from './ContentConfig';
|
||||
import Loading from '../Loading';
|
||||
|
||||
export const MIN_NUMBER_ITEMS = 2;
|
||||
export const MAX_NUMBER_ITEMS = 6;
|
||||
@@ -60,6 +61,10 @@ const Bar = styled.div<{ count: number }>`
|
||||
}px;
|
||||
border-radius: ${theme.borderRadius}px;
|
||||
line-height: 1;
|
||||
|
||||
& .loading {
|
||||
margin: 0 auto;
|
||||
}
|
||||
`}
|
||||
`;
|
||||
|
||||
@@ -181,6 +186,10 @@ export interface MetadataBarProps {
|
||||
* Defaults to "top".
|
||||
*/
|
||||
tooltipPlacement?: TooltipPlacement;
|
||||
/**
|
||||
* Loading state. If true, the bar will display loading spinners.
|
||||
*/
|
||||
loading?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -191,16 +200,21 @@ export interface MetadataBarProps {
|
||||
* To extend the list of content types, a developer needs to request the inclusion of the new type in the design system.
|
||||
* This process is important to make sure the new type is reviewed by the design team, improving Superset consistency.
|
||||
*/
|
||||
const MetadataBar = ({ items, tooltipPlacement = 'top' }: MetadataBarProps) => {
|
||||
const MetadataBar = ({
|
||||
items,
|
||||
tooltipPlacement = 'top',
|
||||
loading = false,
|
||||
}: MetadataBarProps) => {
|
||||
const [width, setWidth] = useState<number>();
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
const uniqueItems = uniqWith(items, (a, b) => a.type === b.type);
|
||||
const sortedItems = uniqueItems.sort((a, b) => ORDER[a.type] - ORDER[b.type]);
|
||||
const count = sortedItems.length;
|
||||
if (count < MIN_NUMBER_ITEMS) {
|
||||
|
||||
if (!loading && count < MIN_NUMBER_ITEMS) {
|
||||
throw Error('The minimum number of items for the metadata bar is 2.');
|
||||
}
|
||||
if (count > MAX_NUMBER_ITEMS) {
|
||||
if (!loading && count > MAX_NUMBER_ITEMS) {
|
||||
throw Error('The maximum number of items for the metadata bar is 6.');
|
||||
}
|
||||
|
||||
@@ -222,16 +236,20 @@ const MetadataBar = ({ items, tooltipPlacement = 'top' }: MetadataBarProps) => {
|
||||
|
||||
return (
|
||||
<Bar ref={ref} count={count} data-test="metadata-bar">
|
||||
{sortedItems.map((item, index) => (
|
||||
<Item
|
||||
barWidth={width}
|
||||
key={index}
|
||||
contentType={item}
|
||||
collapsed={collapsed}
|
||||
last={index === count - 1}
|
||||
tooltipPlacement={tooltipPlacement}
|
||||
/>
|
||||
))}
|
||||
{!loading ? (
|
||||
sortedItems.map((item, index) => (
|
||||
<Item
|
||||
barWidth={width}
|
||||
key={index}
|
||||
contentType={item}
|
||||
collapsed={collapsed}
|
||||
last={index === count - 1}
|
||||
tooltipPlacement={tooltipPlacement}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<Loading position="inline" />
|
||||
)}
|
||||
</Bar>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -40,7 +40,7 @@ import { MenuKeys } from 'src/dashboard/types';
|
||||
const propTypes = {
|
||||
addSuccessToast: PropTypes.func.isRequired,
|
||||
addDangerToast: PropTypes.func.isRequired,
|
||||
dashboardInfo: PropTypes.object.isRequired,
|
||||
dashboardInfo: PropTypes.object,
|
||||
dashboardId: PropTypes.number,
|
||||
dashboardTitle: PropTypes.string,
|
||||
dataMask: PropTypes.object.isRequired,
|
||||
@@ -196,7 +196,7 @@ export class HeaderActionsDropdown extends PureComponent {
|
||||
});
|
||||
|
||||
const refreshIntervalOptions =
|
||||
dashboardInfo.common?.conf?.DASHBOARD_AUTO_REFRESH_INTERVALS;
|
||||
dashboardInfo?.common?.conf?.DASHBOARD_AUTO_REFRESH_INTERVALS;
|
||||
|
||||
const dashboardComponentId = [...(directPathToChild || [])].pop();
|
||||
|
||||
@@ -216,6 +216,7 @@ export class HeaderActionsDropdown extends PureComponent {
|
||||
<Menu.Item
|
||||
key={MenuKeys.ToggleFullscreen}
|
||||
onClick={this.handleMenuClick}
|
||||
disabled={isLoading}
|
||||
>
|
||||
{getUrlParam(URL_PARAMS.standalone)
|
||||
? t('Exit fullscreen')
|
||||
@@ -226,23 +227,25 @@ export class HeaderActionsDropdown extends PureComponent {
|
||||
<Menu.Item
|
||||
key={MenuKeys.EditProperties}
|
||||
onClick={this.handleMenuClick}
|
||||
disabled={isLoading}
|
||||
>
|
||||
{t('Edit properties')}
|
||||
</Menu.Item>
|
||||
)}
|
||||
{editMode && (
|
||||
<Menu.Item key={MenuKeys.EditCss}>
|
||||
<Menu.Item key={MenuKeys.EditCss} disabled={isLoading}>
|
||||
<CssEditor
|
||||
triggerNode={<span>{t('Edit CSS')}</span>}
|
||||
initialCss={this.state.css}
|
||||
onChange={this.changeCss}
|
||||
addDangerToast={addDangerToast}
|
||||
disabled={isLoading}
|
||||
/>
|
||||
</Menu.Item>
|
||||
)}
|
||||
<Menu.Divider />
|
||||
{userCanSave && (
|
||||
<Menu.Item key={MenuKeys.SaveModal}>
|
||||
<Menu.Item key={MenuKeys.SaveModal} disabled={isLoading}>
|
||||
<SaveModal
|
||||
addSuccessToast={this.props.addSuccessToast}
|
||||
addDangerToast={this.props.addDangerToast}
|
||||
@@ -303,6 +306,7 @@ export class HeaderActionsDropdown extends PureComponent {
|
||||
<Menu.Item
|
||||
key={MenuKeys.ManageEmbedded}
|
||||
onClick={this.handleMenuClick}
|
||||
disabled={isLoading}
|
||||
>
|
||||
{t('Embed dashboard')}
|
||||
</Menu.Item>
|
||||
@@ -311,9 +315,12 @@ export class HeaderActionsDropdown extends PureComponent {
|
||||
{!editMode ? (
|
||||
this.state.showReportSubMenu ? (
|
||||
<>
|
||||
<Menu.SubMenu title={t('Manage email report')}>
|
||||
<Menu.SubMenu
|
||||
title={t('Manage email report')}
|
||||
disabled={isLoading}
|
||||
>
|
||||
<HeaderReportDropdown
|
||||
dashboardId={dashboardInfo.id}
|
||||
dashboardId={dashboardInfo?.id}
|
||||
setShowReportSubMenu={this.setShowReportSubMenu}
|
||||
showReportSubMenu={this.state.showReportSubMenu}
|
||||
setIsDropdownVisible={setIsDropdownVisible}
|
||||
@@ -326,17 +333,18 @@ export class HeaderActionsDropdown extends PureComponent {
|
||||
) : (
|
||||
<Menu>
|
||||
<HeaderReportDropdown
|
||||
dashboardId={dashboardInfo.id}
|
||||
dashboardId={dashboardInfo?.id}
|
||||
setShowReportSubMenu={this.setShowReportSubMenu}
|
||||
setIsDropdownVisible={setIsDropdownVisible}
|
||||
isDropdownVisible={isDropdownVisible}
|
||||
useTextMenu
|
||||
disabled={isLoading}
|
||||
/>
|
||||
</Menu>
|
||||
)
|
||||
) : null}
|
||||
{editMode && !isEmpty(dashboardInfo?.metadata?.filter_scopes) && (
|
||||
<Menu.Item key={MenuKeys.SetFilterMapping}>
|
||||
<Menu.Item key={MenuKeys.SetFilterMapping} disabled={isLoading}>
|
||||
<FilterScopeModal
|
||||
className="m-r-5"
|
||||
triggerNode={t('Set filter mapping')}
|
||||
@@ -344,7 +352,7 @@ export class HeaderActionsDropdown extends PureComponent {
|
||||
</Menu.Item>
|
||||
)}
|
||||
|
||||
<Menu.Item key={MenuKeys.AutorefreshModal}>
|
||||
<Menu.Item key={MenuKeys.AutorefreshModal} disabled={isLoading}>
|
||||
<RefreshIntervalModal
|
||||
addSuccessToast={this.props.addSuccessToast}
|
||||
refreshFrequency={refreshFrequency}
|
||||
|
||||
@@ -65,10 +65,11 @@ const propTypes = {
|
||||
addDangerToast: PropTypes.func.isRequired,
|
||||
addWarningToast: PropTypes.func.isRequired,
|
||||
user: PropTypes.object, // UserWithPermissionsAndRoles,
|
||||
dashboardInfo: PropTypes.object.isRequired,
|
||||
dashboardInfo: PropTypes.object,
|
||||
dashboardTitle: PropTypes.string,
|
||||
dataMask: PropTypes.object.isRequired,
|
||||
charts: PropTypes.objectOf(chartPropShape).isRequired,
|
||||
chartsLoading: PropTypes.bool.isRequired,
|
||||
layout: PropTypes.object.isRequired,
|
||||
expandedSlices: PropTypes.object,
|
||||
customCss: PropTypes.string,
|
||||
@@ -78,7 +79,6 @@ const propTypes = {
|
||||
setUnsavedChanges: PropTypes.func.isRequired,
|
||||
isStarred: PropTypes.bool.isRequired,
|
||||
isPublished: PropTypes.bool.isRequired,
|
||||
isLoading: PropTypes.bool.isRequired,
|
||||
onSave: PropTypes.func.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
fetchFaveStar: PropTypes.func.isRequired,
|
||||
@@ -270,7 +270,7 @@ class Header extends PureComponent {
|
||||
}
|
||||
|
||||
forceRefresh() {
|
||||
if (!this.props.isLoading) {
|
||||
if (!this.props.chartsLoading) {
|
||||
const chartList = Object.keys(this.props.charts);
|
||||
this.props.logEvent(LOG_ACTIONS_FORCE_REFRESH_DASHBOARD, {
|
||||
force: true,
|
||||
@@ -288,10 +288,10 @@ class Header extends PureComponent {
|
||||
}
|
||||
|
||||
startPeriodicRender(interval) {
|
||||
let intervalMessage;
|
||||
const { dashboardInfo } = this.props;
|
||||
|
||||
if (interval) {
|
||||
const { dashboardInfo } = this.props;
|
||||
let intervalMessage;
|
||||
if (interval && dashboardInfo) {
|
||||
const periodicRefreshOptions =
|
||||
dashboardInfo.common?.conf?.DASHBOARD_AUTO_REFRESH_INTERVALS;
|
||||
const predefinedValue = periodicRefreshOptions.find(
|
||||
@@ -340,11 +340,13 @@ class Header extends PureComponent {
|
||||
);
|
||||
};
|
||||
|
||||
this.refreshTimer = setPeriodicRunner({
|
||||
interval,
|
||||
periodicRender,
|
||||
refreshTimer: this.refreshTimer,
|
||||
});
|
||||
if (dashboardInfo) {
|
||||
this.refreshTimer = setPeriodicRunner({
|
||||
interval,
|
||||
periodicRender,
|
||||
refreshTimer: this.refreshTimer,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
toggleEditMode() {
|
||||
@@ -388,7 +390,7 @@ class Header extends PureComponent {
|
||||
roles: dashboardInfo.roles,
|
||||
slug,
|
||||
metadata: {
|
||||
...dashboardInfo?.metadata,
|
||||
...dashboardInfo.metadata,
|
||||
color_namespace: currentColorNamespace,
|
||||
color_scheme: currentColorScheme,
|
||||
positions,
|
||||
@@ -434,28 +436,32 @@ class Header extends PureComponent {
|
||||
|
||||
getMetadataItems = () => {
|
||||
const { dashboardInfo } = this.props;
|
||||
|
||||
return [
|
||||
{
|
||||
type: MetadataType.LastModified,
|
||||
value: dashboardInfo.changed_on_delta_humanized,
|
||||
value: dashboardInfo?.changed_on_delta_humanized,
|
||||
modifiedBy:
|
||||
getOwnerName(dashboardInfo.changed_by) || t('Not available'),
|
||||
getOwnerName(dashboardInfo?.changed_by) || t('Not available'),
|
||||
},
|
||||
{
|
||||
type: MetadataType.Owner,
|
||||
createdBy: getOwnerName(dashboardInfo.created_by) || t('Not available'),
|
||||
createdBy:
|
||||
getOwnerName(dashboardInfo?.created_by) || t('Not available'),
|
||||
owners:
|
||||
dashboardInfo.owners.length > 0
|
||||
? dashboardInfo.owners.map(getOwnerName)
|
||||
dashboardInfo?.owners.length > 0
|
||||
? dashboardInfo?.owners.map(getOwnerName)
|
||||
: t('None'),
|
||||
createdOn: dashboardInfo.created_on_delta_humanized,
|
||||
createdOn: dashboardInfo?.created_on_delta_humanized,
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
dashboardInfo,
|
||||
dashboardTitle,
|
||||
chartsLoading,
|
||||
layout,
|
||||
expandedSlices,
|
||||
customCss,
|
||||
@@ -474,27 +480,24 @@ class Header extends PureComponent {
|
||||
editMode,
|
||||
isPublished,
|
||||
user,
|
||||
dashboardInfo,
|
||||
hasUnsavedChanges,
|
||||
isLoading,
|
||||
refreshFrequency,
|
||||
shouldPersistRefreshFrequency,
|
||||
setRefreshFrequency,
|
||||
lastModifiedTime,
|
||||
logEvent,
|
||||
} = this.props;
|
||||
|
||||
const userCanEdit =
|
||||
dashboardInfo.dash_edit_perm && !dashboardInfo.is_managed_externally;
|
||||
const userCanShare = dashboardInfo.dash_share_perm;
|
||||
const userCanSaveAs = dashboardInfo.dash_save_perm;
|
||||
dashboardInfo?.dash_edit_perm && !dashboardInfo?.is_managed_externally;
|
||||
const userCanShare = dashboardInfo?.dash_share_perm;
|
||||
const userCanSaveAs = dashboardInfo?.dash_save_perm;
|
||||
const userCanCurate =
|
||||
isFeatureEnabled(FeatureFlag.EmbeddedSuperset) &&
|
||||
findPermission('can_set_embedded', 'Dashboard', user.roles);
|
||||
const refreshLimit =
|
||||
dashboardInfo.common?.conf?.SUPERSET_DASHBOARD_PERIODICAL_REFRESH_LIMIT;
|
||||
dashboardInfo?.common?.conf?.SUPERSET_DASHBOARD_PERIODICAL_REFRESH_LIMIT;
|
||||
const refreshWarning =
|
||||
dashboardInfo.common?.conf
|
||||
dashboardInfo?.common?.conf
|
||||
?.SUPERSET_DASHBOARD_PERIODICAL_REFRESH_WARNING_MESSAGE;
|
||||
|
||||
const handleOnPropertiesChange = updates => {
|
||||
@@ -519,44 +522,47 @@ class Header extends PureComponent {
|
||||
<div
|
||||
css={headerContainerStyle}
|
||||
data-test="dashboard-header-container"
|
||||
data-test-id={dashboardInfo.id}
|
||||
data-test-id={dashboardInfo?.id}
|
||||
className="dashboard-header-container"
|
||||
>
|
||||
<PageHeaderWithActions
|
||||
editableTitleProps={{
|
||||
title: dashboardTitle,
|
||||
canEdit: userCanEdit && editMode,
|
||||
canEdit: !!(userCanEdit && editMode),
|
||||
onSave: this.handleChangeText,
|
||||
placeholder: t('Add the name of the dashboard'),
|
||||
label: t('Dashboard title'),
|
||||
showTooltip: false,
|
||||
loading: !dashboardTitle,
|
||||
}}
|
||||
certificatiedBadgeProps={{
|
||||
certifiedBy: dashboardInfo.certified_by,
|
||||
details: dashboardInfo.certification_details,
|
||||
certifiedBy: dashboardInfo?.certified_by,
|
||||
details: dashboardInfo?.certification_details,
|
||||
}}
|
||||
faveStarProps={{
|
||||
itemId: dashboardInfo.id,
|
||||
itemId: dashboardInfo?.id,
|
||||
fetchFaveStar: this.props.fetchFaveStar,
|
||||
saveFaveStar: this.props.saveFaveStar,
|
||||
isStarred: this.props.isStarred,
|
||||
showTooltip: true,
|
||||
showTooltip: !!dashboardInfo?.id,
|
||||
}}
|
||||
titlePanelAdditionalItems={[
|
||||
!editMode && (
|
||||
<PublishedStatus
|
||||
dashboardId={dashboardInfo.id}
|
||||
dashboardId={dashboardInfo?.id}
|
||||
isPublished={isPublished}
|
||||
savePublished={this.props.savePublished}
|
||||
canEdit={userCanEdit}
|
||||
canSave={userCanSaveAs}
|
||||
visible={!editMode}
|
||||
loading={!dashboardInfo?.id}
|
||||
/>
|
||||
),
|
||||
!editMode && (
|
||||
<MetadataBar
|
||||
items={this.getMetadataItems()}
|
||||
tooltipPlacement="bottom"
|
||||
loading={!dashboardInfo?.id}
|
||||
/>
|
||||
),
|
||||
]}
|
||||
@@ -576,7 +582,7 @@ class Header extends PureComponent {
|
||||
>
|
||||
<StyledUndoRedoButton
|
||||
buttonStyle="link"
|
||||
disabled={undoLength < 1}
|
||||
disabled={undoLength < 1 || !dashboardInfo}
|
||||
onClick={undoLength && onUndo}
|
||||
>
|
||||
<Icons.Undo
|
||||
@@ -596,7 +602,7 @@ class Header extends PureComponent {
|
||||
>
|
||||
<StyledUndoRedoButton
|
||||
buttonStyle="link"
|
||||
disabled={redoLength < 1}
|
||||
disabled={redoLength < 1 || !dashboardInfo}
|
||||
onClick={redoLength && onRedo}
|
||||
>
|
||||
<Icons.Redo
|
||||
@@ -618,17 +624,20 @@ class Header extends PureComponent {
|
||||
buttonStyle="default"
|
||||
data-test="discard-changes-button"
|
||||
aria-label={t('Discard')}
|
||||
disabled={!hasUnsavedChanges}
|
||||
loading={!dashboardInfo}
|
||||
>
|
||||
{t('Discard')}
|
||||
</Button>
|
||||
<Button
|
||||
aria-label={t('Save')}
|
||||
css={saveBtnStyle}
|
||||
buttonSize="small"
|
||||
disabled={!hasUnsavedChanges}
|
||||
buttonStyle="primary"
|
||||
onClick={this.overwriteDashboard}
|
||||
data-test="header-save-button"
|
||||
aria-label={t('Save')}
|
||||
disabled={!hasUnsavedChanges}
|
||||
loading={!dashboardInfo}
|
||||
>
|
||||
{t('Save')}
|
||||
</Button>
|
||||
@@ -670,7 +679,7 @@ class Header extends PureComponent {
|
||||
<ConnectedHeaderActionsDropdown
|
||||
addSuccessToast={this.props.addSuccessToast}
|
||||
addDangerToast={this.props.addDangerToast}
|
||||
dashboardId={dashboardInfo.id}
|
||||
dashboardId={dashboardInfo?.id}
|
||||
dashboardTitle={dashboardTitle}
|
||||
dashboardInfo={dashboardInfo}
|
||||
dataMask={dataMask}
|
||||
@@ -693,7 +702,7 @@ class Header extends PureComponent {
|
||||
userCanShare={userCanShare}
|
||||
userCanSave={userCanSaveAs}
|
||||
userCanCurate={userCanCurate}
|
||||
isLoading={isLoading}
|
||||
isLoading={chartsLoading || !dashboardInfo?.id}
|
||||
showPropertiesModal={this.showPropertiesModal}
|
||||
manageEmbedded={this.showEmbedModal}
|
||||
refreshLimit={refreshLimit}
|
||||
@@ -704,12 +713,12 @@ class Header extends PureComponent {
|
||||
logEvent={logEvent}
|
||||
/>
|
||||
}
|
||||
showFaveStar={user?.userId && dashboardInfo?.id}
|
||||
showFaveStar={user?.userId}
|
||||
showTitlePanelItems
|
||||
/>
|
||||
{this.state.showingPropertiesModal && (
|
||||
<PropertiesModal
|
||||
dashboardId={dashboardInfo.id}
|
||||
dashboardId={dashboardInfo?.id}
|
||||
dashboardInfo={dashboardInfo}
|
||||
dashboardTitle={dashboardTitle}
|
||||
show={this.state.showingPropertiesModal}
|
||||
@@ -726,7 +735,7 @@ class Header extends PureComponent {
|
||||
<DashboardEmbedModal
|
||||
show={this.state.showingEmbedModal}
|
||||
onHide={this.hideEmbedModal}
|
||||
dashboardId={dashboardInfo.id}
|
||||
dashboardId={dashboardInfo?.id}
|
||||
/>
|
||||
)}
|
||||
<Global
|
||||
|
||||
@@ -28,6 +28,7 @@ const propTypes = {
|
||||
savePublished: PropTypes.func.isRequired,
|
||||
canEdit: PropTypes.bool,
|
||||
canSave: PropTypes.bool,
|
||||
loading: PropTypes.bool,
|
||||
};
|
||||
|
||||
const draftButtonTooltip = t(
|
||||
@@ -54,6 +55,9 @@ export default class PublishedStatus extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.props.loading) {
|
||||
return <Label>{t('...')}</Label>;
|
||||
}
|
||||
// Show everybody the draft badge
|
||||
if (!this.props.isPublished) {
|
||||
// if they can edit the dash, make the badge a button
|
||||
|
||||
@@ -50,16 +50,16 @@ const InnerStyledDiv = styled.div`
|
||||
type RefreshIntervalModalProps = {
|
||||
addSuccessToast: (msg: string) => void;
|
||||
triggerNode: JSX.Element;
|
||||
refreshFrequency: number;
|
||||
refreshFrequency?: number;
|
||||
onChange: (refreshLimit: number, editMode: boolean) => void;
|
||||
editMode: boolean;
|
||||
refreshLimit?: number;
|
||||
refreshWarning: string | null;
|
||||
refreshIntervalOptions: [number, string][];
|
||||
refreshIntervalOptions?: [number, string][];
|
||||
};
|
||||
|
||||
type RefreshIntervalModalState = {
|
||||
refreshFrequency: number;
|
||||
refreshFrequency?: number;
|
||||
custom_hour: number;
|
||||
custom_min: number;
|
||||
custom_sec: number;
|
||||
@@ -93,9 +93,11 @@ class RefreshIntervalModal extends PureComponent<
|
||||
}
|
||||
|
||||
onSave() {
|
||||
this.props.onChange(this.state.refreshFrequency, this.props.editMode);
|
||||
this.modalRef?.current?.close();
|
||||
this.props.addSuccessToast(t('Refresh interval saved'));
|
||||
if (this.state.refreshFrequency !== undefined) {
|
||||
this.props.onChange(this.state.refreshFrequency, this.props.editMode);
|
||||
this.modalRef?.current?.close();
|
||||
this.props.addSuccessToast(t('Refresh interval saved'));
|
||||
}
|
||||
}
|
||||
|
||||
onCancel() {
|
||||
@@ -108,7 +110,7 @@ class RefreshIntervalModal extends PureComponent<
|
||||
handleFrequencyChange(value: number) {
|
||||
const { refreshIntervalOptions } = this.props;
|
||||
this.setState({
|
||||
refreshFrequency: value || refreshIntervalOptions[0][0],
|
||||
refreshFrequency: value || refreshIntervalOptions?.[0]?.[0],
|
||||
});
|
||||
|
||||
this.setState({
|
||||
@@ -135,7 +137,7 @@ class RefreshIntervalModal extends PureComponent<
|
||||
|
||||
refresh_options.push({ value: -1, label: t('Custom interval') });
|
||||
refresh_options.push(
|
||||
...refreshIntervalOptions.map(option => ({
|
||||
...refreshIntervalOptions?.map(option => ({
|
||||
value: option[0],
|
||||
label: t(option[1]),
|
||||
})),
|
||||
@@ -221,7 +223,11 @@ class RefreshIntervalModal extends PureComponent<
|
||||
</FormLabel>
|
||||
<Select
|
||||
ariaLabel={t('Refresh interval')}
|
||||
options={this.createIntervalOptions(refreshIntervalOptions)}
|
||||
options={
|
||||
refreshIntervalOptions
|
||||
? this.createIntervalOptions(refreshIntervalOptions)
|
||||
: []
|
||||
}
|
||||
value={refreshFrequency}
|
||||
onChange={this.handleFrequencyChange}
|
||||
sortComparator={propertyComparator('value')}
|
||||
@@ -307,6 +313,7 @@ class RefreshIntervalModal extends PureComponent<
|
||||
<Button
|
||||
buttonStyle="primary"
|
||||
buttonSize="small"
|
||||
disabled={!refreshIntervalOptions}
|
||||
onClick={() =>
|
||||
this.refresh_custom_val(
|
||||
custom_block,
|
||||
|
||||
@@ -87,7 +87,7 @@ function mapStateToProps({
|
||||
user,
|
||||
isStarred: !!dashboardState.isStarred,
|
||||
isPublished: !!dashboardState.isPublished,
|
||||
isLoading: isDashboardLoading(charts),
|
||||
chartsLoading: isDashboardLoading(charts),
|
||||
hasUnsavedChanges: !!dashboardState.hasUnsavedChanges,
|
||||
maxUndoHistoryExceeded: !!dashboardState.maxUndoHistoryExceeded,
|
||||
lastModifiedTime: Math.max(
|
||||
|
||||
Reference in New Issue
Block a user