/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ import React from 'react'; import PropTypes from 'prop-types'; import moment from 'moment'; import { styled, t } from '@superset-ui/core'; import { Menu, NoAnimationDropdown } from 'src/common/components'; import URLShortLinkModal from '../../components/URLShortLinkModal'; import downloadAsImage from '../../utils/downloadAsImage'; import getDashboardUrl from '../util/getDashboardUrl'; import { getActiveFilters } from '../util/activeDashboardFilters'; const propTypes = { slice: PropTypes.object.isRequired, componentId: PropTypes.string.isRequired, dashboardId: PropTypes.number.isRequired, addDangerToast: PropTypes.func.isRequired, isCached: PropTypes.arrayOf(PropTypes.bool), cachedDttm: PropTypes.arrayOf(PropTypes.string), isExpanded: PropTypes.bool, updatedDttm: PropTypes.number, supersetCanExplore: PropTypes.bool, supersetCanCSV: PropTypes.bool, sliceCanEdit: PropTypes.bool, toggleExpandSlice: PropTypes.func, forceRefresh: PropTypes.func, exploreChart: PropTypes.func, exportCSV: PropTypes.func, }; const defaultProps = { forceRefresh: () => ({}), toggleExpandSlice: () => ({}), exploreChart: () => ({}), exportCSV: () => ({}), cachedDttm: [], updatedDttm: null, isCached: [], isExpanded: false, supersetCanExplore: false, supersetCanCSV: false, sliceCanEdit: false, }; const MENU_KEYS = { FORCE_REFRESH: 'force_refresh', TOGGLE_CHART_DESCRIPTION: 'toggle_chart_description', EXPLORE_CHART: 'explore_chart', EXPORT_CSV: 'export_csv', RESIZE_LABEL: 'resize_label', SHARE_CHART: 'share_chart', DOWNLOAD_AS_IMAGE: 'download_as_image', }; const VerticalDotsContainer = styled.div` padding: ${({ theme }) => theme.gridUnit / 4}px ${({ theme }) => theme.gridUnit * 1.5}px; .dot { display: block; } &:hover { cursor: pointer; } `; const RefreshTooltip = styled.div` height: auto; margin: ${({ theme }) => theme.gridUnit}px 0; color: ${({ theme }) => theme.colors.grayscale.base}; line-height: ${({ theme }) => theme.typography.sizes.m * 1.5}px; display: flex; flex-direction: column; align-items: flex-start; justify-content: flex-start; `; const SCREENSHOT_NODE_SELECTOR = '.dashboard-component-chart-holder'; const VerticalDotsTrigger = () => ( ); class SliceHeaderControls extends React.PureComponent { constructor(props) { super(props); this.toggleControls = this.toggleControls.bind(this); this.refreshChart = this.refreshChart.bind(this); this.handleMenuClick = this.handleMenuClick.bind(this); this.state = { showControls: false, }; } refreshChart() { if (this.props.updatedDttm) { this.props.forceRefresh( this.props.slice.slice_id, this.props.dashboardId, ); } } toggleControls() { this.setState(prevState => ({ showControls: !prevState.showControls, })); } handleMenuClick({ key, domEvent }) { switch (key) { case MENU_KEYS.FORCE_REFRESH: this.refreshChart(); break; case MENU_KEYS.TOGGLE_CHART_DESCRIPTION: this.props.toggleExpandSlice(this.props.slice.slice_id); break; case MENU_KEYS.EXPLORE_CHART: this.props.exploreChart(this.props.slice.slice_id); break; case MENU_KEYS.EXPORT_CSV: this.props.exportCSV(this.props.slice.slice_id); break; case MENU_KEYS.RESIZE_LABEL: this.props.handleToggleFullSize(); break; case MENU_KEYS.DOWNLOAD_AS_IMAGE: { // menu closes with a delay, we need to hide it manually, // so that we don't capture it on the screenshot const menu = document.querySelector( '.ant-dropdown:not(.ant-dropdown-hidden)', ); menu.style.visibility = 'hidden'; downloadAsImage( SCREENSHOT_NODE_SELECTOR, this.props.slice.slice_name, )(domEvent).then(() => { menu.style.visibility = 'visible'; }); break; } default: break; } } render() { const { slice, isCached, cachedDttm, updatedDttm, componentId, addDangerToast, isFullSize, } = this.props; const cachedWhen = cachedDttm.map(itemCachedDttm => moment.utc(itemCachedDttm).fromNow(), ); const updatedWhen = updatedDttm ? moment.utc(updatedDttm).fromNow() : ''; const getCachedTitle = itemCached => { if (itemCached) { return t('Cached %s', cachedWhen); } if (updatedWhen) { return t('Fetched %s', updatedWhen); } return ''; }; const refreshTooltipData = isCached.map(getCachedTitle) || ''; // If all queries have same cache time we can unit them to one let refreshTooltip = [...new Set(refreshTooltipData)]; refreshTooltip = refreshTooltip.map((item, index) => (
{refreshTooltip.length > 1 ? `${t('Query')} ${index + 1}: ${item}` : item}
)); const resizeLabel = isFullSize ? t('Minimize Chart') : t('Maximize Chart'); const menu = ( {t('Force refresh')} {refreshTooltip} {slice.description && ( {t('Toggle chart description')} )} {this.props.supersetCanExplore && ( {t('View Chart in Explore')} )} {t('Share chart')}} /> {resizeLabel} {t('Download as image')} {this.props.supersetCanCSV && ( {t('Export CSV')} )} ); return ( triggerNode.closest(SCREENSHOT_NODE_SELECTOR) } > ); } } SliceHeaderControls.propTypes = propTypes; SliceHeaderControls.defaultProps = defaultProps; export default SliceHeaderControls;