feat(style): hide dashboard header by url parameter (#12918)
* feat(native-filters): hide dashboard header bu url parameter * lint: fix lint * test: add tests * test: fix test * refactor: upgrade standalone param * fix: pre-commit and extract to method is_standalone_mode * test: fix tests * test: fix tests * fix: fix standalone statement * refactor: fix CR notes * chore: pre-commit * fix: fix sticky tabs + update CR notes * lint: fix lint * lint: fix lint * fix: fix CR notes * fix: fix CR notes * lint: fix lint * refactor: fix cr notes Co-authored-by: amitmiran137 <amit.miran@nielsen.com>
This commit is contained in:
parent
42c4facb7e
commit
c5781cde60
|
|
@ -27,13 +27,14 @@ describe('AnchorLink', () => {
|
|||
anchorLinkId: 'CHART-123',
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
global.window = Object.create(window);
|
||||
Object.defineProperty(window, 'location', {
|
||||
value: {
|
||||
hash: `#${props.anchorLinkId}`,
|
||||
},
|
||||
const globalLocation = window.location;
|
||||
afterEach(() => {
|
||||
window.location = globalLocation;
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
delete window.location;
|
||||
window.location = new URL(`https://path?#${props.anchorLinkId}`);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
|
|
|||
|
|
@ -18,28 +18,51 @@
|
|||
*/
|
||||
import getDashboardUrl from 'src/dashboard/util/getDashboardUrl';
|
||||
import { DASHBOARD_FILTER_SCOPE_GLOBAL } from 'src/dashboard/reducers/dashboardFilters';
|
||||
import { DashboardStandaloneMode } from '../../../../src/dashboard/util/constants';
|
||||
|
||||
describe('getChartIdsFromLayout', () => {
|
||||
it('should encode filters', () => {
|
||||
const filters = {
|
||||
'35_key': {
|
||||
values: ['value'],
|
||||
scope: DASHBOARD_FILTER_SCOPE_GLOBAL,
|
||||
},
|
||||
};
|
||||
|
||||
const globalLocation = window.location;
|
||||
afterEach(() => {
|
||||
window.location = globalLocation;
|
||||
});
|
||||
|
||||
it('should encode filters', () => {
|
||||
const url = getDashboardUrl('path', filters);
|
||||
expect(url).toBe(
|
||||
'path?preselect_filters=%7B%2235%22%3A%7B%22key%22%3A%5B%22value%22%5D%7D%7D',
|
||||
);
|
||||
});
|
||||
|
||||
it('should encode filters with hash', () => {
|
||||
const urlWithHash = getDashboardUrl('path', filters, 'iamhashtag');
|
||||
expect(urlWithHash).toBe(
|
||||
'path?preselect_filters=%7B%2235%22%3A%7B%22key%22%3A%5B%22value%22%5D%7D%7D#iamhashtag',
|
||||
);
|
||||
});
|
||||
|
||||
const urlWithStandalone = getDashboardUrl('path', filters, '', true);
|
||||
it('should encode filters with standalone', () => {
|
||||
const urlWithStandalone = getDashboardUrl(
|
||||
'path',
|
||||
filters,
|
||||
'',
|
||||
DashboardStandaloneMode.HIDE_NAV,
|
||||
);
|
||||
expect(urlWithStandalone).toBe(
|
||||
'path?preselect_filters=%7B%2235%22%3A%7B%22key%22%3A%5B%22value%22%5D%7D%7D&standalone=true',
|
||||
`path?preselect_filters=%7B%2235%22%3A%7B%22key%22%3A%5B%22value%22%5D%7D%7D&standalone=${DashboardStandaloneMode.HIDE_NAV}`,
|
||||
);
|
||||
});
|
||||
|
||||
it('should encode filters with missing standalone', () => {
|
||||
const urlWithStandalone = getDashboardUrl('path', filters, '', null);
|
||||
expect(urlWithStandalone).toBe(
|
||||
'path?preselect_filters=%7B%2235%22%3A%7B%22key%22%3A%5B%22value%22%5D%7D%7D',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -26,7 +26,8 @@ import configureStore from 'redux-mock-store';
|
|||
import fetchMock from 'fetch-mock';
|
||||
import EmbedCodeButton from 'src/explore/components/EmbedCodeButton';
|
||||
import * as exploreUtils from 'src/explore/exploreUtils';
|
||||
import * as common from 'src/utils/common';
|
||||
import * as urlUtils from 'src/utils/urlUtils';
|
||||
import { DashboardStandaloneMode } from 'src/dashboard/util/constants';
|
||||
|
||||
const ENDPOINT = 'glob:*/r/shortner/';
|
||||
|
||||
|
|
@ -53,7 +54,7 @@ describe('EmbedCodeButton', () => {
|
|||
|
||||
it('should create a short, standalone, explore url', () => {
|
||||
const spy1 = sinon.spy(exploreUtils, 'getExploreLongUrl');
|
||||
const spy2 = sinon.spy(common, 'getShortUrl');
|
||||
const spy2 = sinon.spy(urlUtils, 'getShortUrl');
|
||||
|
||||
const wrapper = mount(
|
||||
<ThemeProvider theme={supersetTheme}>
|
||||
|
|
@ -92,15 +93,17 @@ describe('EmbedCodeButton', () => {
|
|||
shortUrlId: 100,
|
||||
});
|
||||
const embedHTML =
|
||||
`${
|
||||
'<iframe\n' +
|
||||
' width="2000"\n' +
|
||||
' height="1000"\n' +
|
||||
' seamless\n' +
|
||||
' frameBorder="0"\n' +
|
||||
' scrolling="no"\n' +
|
||||
' src="http://localhostendpoint_url?r=100&standalone=true&height=1000"\n' +
|
||||
'>\n' +
|
||||
'</iframe>';
|
||||
' src="http://localhostendpoint_url?r=100&standalone='
|
||||
}${DashboardStandaloneMode.HIDE_NAV}&height=1000"\n` +
|
||||
`>\n` +
|
||||
`</iframe>`;
|
||||
expect(wrapper.instance().generateEmbedHTML()).toBe(embedHTML);
|
||||
stub.restore();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import {
|
|||
buildTimeRangeString,
|
||||
formatTimeRange,
|
||||
} from 'src/explore/dateFilterUtils';
|
||||
import { DashboardStandaloneMode } from 'src/dashboard/util/constants';
|
||||
import * as hostNamesConfig from 'src/utils/hostNamesConfig';
|
||||
import { getChartMetadataRegistry } from '@superset-ui/core';
|
||||
|
||||
|
|
@ -99,7 +100,9 @@ describe('exploreUtils', () => {
|
|||
});
|
||||
compareURI(
|
||||
URI(url),
|
||||
URI('/superset/explore/').search({ standalone: 'true' }),
|
||||
URI('/superset/explore/').search({
|
||||
standalone: DashboardStandaloneMode.HIDE_NAV,
|
||||
}),
|
||||
);
|
||||
});
|
||||
it('preserves main URLs params', () => {
|
||||
|
|
@ -205,7 +208,7 @@ describe('exploreUtils', () => {
|
|||
URI(getExploreLongUrl(formData, 'standalone')),
|
||||
URI('/superset/explore/').search({
|
||||
form_data: sFormData,
|
||||
standalone: 'true',
|
||||
standalone: DashboardStandaloneMode.HIDE_NAV,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import PropTypes from 'prop-types';
|
|||
import { t } from '@superset-ui/core';
|
||||
import Popover from 'src/common/components/Popover';
|
||||
import CopyToClipboard from './CopyToClipboard';
|
||||
import { getShortUrl } from '../utils/common';
|
||||
import { getShortUrl } from '../utils/urlUtils';
|
||||
import withToasts from '../messageToasts/enhancers/withToasts';
|
||||
|
||||
const propTypes = {
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
import React from 'react';
|
||||
import { t } from '@superset-ui/core';
|
||||
import CopyToClipboard from './CopyToClipboard';
|
||||
import { getShortUrl } from '../utils/common';
|
||||
import { getShortUrl } from '../utils/urlUtils';
|
||||
import withToasts from '../messageToasts/enhancers/withToasts';
|
||||
import ModalTrigger from './ModalTrigger';
|
||||
|
||||
|
|
|
|||
|
|
@ -22,3 +22,8 @@ export const TIME_WITH_MS = 'HH:mm:ss.SSS';
|
|||
|
||||
export const BOOL_TRUE_DISPLAY = 'True';
|
||||
export const BOOL_FALSE_DISPLAY = 'False';
|
||||
|
||||
export const URL_PARAMS = {
|
||||
standalone: 'standalone',
|
||||
preselectFilters: 'preselect_filters',
|
||||
};
|
||||
|
|
|
|||
|
|
@ -42,13 +42,16 @@ import findTabIndexByComponentId from 'src/dashboard/util/findTabIndexByComponen
|
|||
import getDirectPathToTabIndex from 'src/dashboard/util/getDirectPathToTabIndex';
|
||||
import getLeafComponentIdFromPath from 'src/dashboard/util/getLeafComponentIdFromPath';
|
||||
import { FeatureFlag, isFeatureEnabled } from 'src/featureFlags';
|
||||
import { URL_PARAMS } from 'src/constants';
|
||||
import {
|
||||
DASHBOARD_GRID_ID,
|
||||
DASHBOARD_ROOT_ID,
|
||||
DASHBOARD_ROOT_DEPTH,
|
||||
DashboardStandaloneMode,
|
||||
} from '../util/constants';
|
||||
import FilterBar from './nativeFilters/FilterBar/FilterBar';
|
||||
import { StickyVerticalBar } from './StickyVerticalBar';
|
||||
import { getUrlParam } from '../../utils/urlUtils';
|
||||
|
||||
const TABS_HEIGHT = 47;
|
||||
const HEADER_HEIGHT = 67;
|
||||
|
|
@ -225,7 +228,13 @@ class DashboardBuilder extends React.Component {
|
|||
|
||||
const childIds = topLevelTabs ? topLevelTabs.children : [DASHBOARD_GRID_ID];
|
||||
|
||||
const barTopOffset = HEADER_HEIGHT + (topLevelTabs ? TABS_HEIGHT : 0);
|
||||
const hideDashboardHeader =
|
||||
getUrlParam(URL_PARAMS.standalone, 'number') ===
|
||||
DashboardStandaloneMode.HIDE_NAV_AND_TITLE;
|
||||
|
||||
const barTopOffset =
|
||||
(hideDashboardHeader ? 0 : HEADER_HEIGHT) +
|
||||
(topLevelTabs ? TABS_HEIGHT : 0);
|
||||
|
||||
return (
|
||||
<StickyContainer
|
||||
|
|
@ -243,11 +252,14 @@ class DashboardBuilder extends React.Component {
|
|||
editMode={editMode}
|
||||
// you cannot drop on/displace tabs if they already exist
|
||||
disableDragdrop={!!topLevelTabs}
|
||||
style={{ zIndex: 100, ...style }}
|
||||
style={{
|
||||
zIndex: 100,
|
||||
...style,
|
||||
}}
|
||||
>
|
||||
{({ dropIndicatorProps }) => (
|
||||
<div>
|
||||
<DashboardHeader />
|
||||
{!hideDashboardHeader && <DashboardHeader />}
|
||||
{dropIndicatorProps && <div {...dropIndicatorProps} />}
|
||||
{topLevelTabs && (
|
||||
<WithPopoverMenu
|
||||
|
|
@ -277,7 +289,6 @@ class DashboardBuilder extends React.Component {
|
|||
</DragDroppable>
|
||||
)}
|
||||
</Sticky>
|
||||
|
||||
<StyledDashboardContent
|
||||
className="dashboard-content"
|
||||
dashboardFiltersOpen={this.state.dashboardFiltersOpen}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import { styled, SupersetClient, t } from '@superset-ui/core';
|
|||
|
||||
import { Menu, NoAnimationDropdown } from 'src/common/components';
|
||||
import Icon from 'src/components/Icon';
|
||||
|
||||
import { URL_PARAMS } from 'src/constants';
|
||||
import CssEditor from './CssEditor';
|
||||
import RefreshIntervalModal from './RefreshIntervalModal';
|
||||
import SaveModal from './SaveModal';
|
||||
|
|
@ -34,6 +34,7 @@ import FilterScopeModal from './filterscope/FilterScopeModal';
|
|||
import downloadAsImage from '../../utils/downloadAsImage';
|
||||
import getDashboardUrl from '../util/getDashboardUrl';
|
||||
import { getActiveFilters } from '../util/activeDashboardFilters';
|
||||
import { getUrlParam } from '../../utils/urlUtils';
|
||||
|
||||
const propTypes = {
|
||||
addSuccessToast: PropTypes.func.isRequired,
|
||||
|
|
@ -162,14 +163,11 @@ class HeaderActionsDropdown extends React.PureComponent {
|
|||
break;
|
||||
}
|
||||
case MENU_KEYS.TOGGLE_FULLSCREEN: {
|
||||
const hasStandalone = window.location.search.includes(
|
||||
'standalone=true',
|
||||
);
|
||||
const url = getDashboardUrl(
|
||||
window.location.pathname,
|
||||
getActiveFilters(),
|
||||
window.location.hash,
|
||||
!hasStandalone,
|
||||
getUrlParam(URL_PARAMS.standalone, 'number'),
|
||||
);
|
||||
window.location.replace(url);
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -22,8 +22,6 @@ import { StickyContainer, Sticky } from 'react-sticky';
|
|||
import { styled } from '@superset-ui/core';
|
||||
import cx from 'classnames';
|
||||
|
||||
export const SUPERSET_HEADER_HEIGHT = 59;
|
||||
|
||||
const Wrapper = styled.div`
|
||||
position: relative;
|
||||
width: ${({ theme }) => theme.gridUnit * 8}px;
|
||||
|
|
|
|||
|
|
@ -69,3 +69,8 @@ export const IN_COMPONENT_ELEMENT_TYPES = ['LABEL'];
|
|||
|
||||
// filter scope selector filter fields pane root id
|
||||
export const ALL_FILTERS_ROOT = 'ALL_FILTERS_ROOT';
|
||||
|
||||
export enum DashboardStandaloneMode {
|
||||
HIDE_NAV = 1,
|
||||
HIDE_NAV_AND_TITLE = 2,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,19 +16,29 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { URL_PARAMS } from 'src/constants';
|
||||
import serializeActiveFilterValues from './serializeActiveFilterValues';
|
||||
|
||||
export default function getDashboardUrl(
|
||||
pathname,
|
||||
pathname: string,
|
||||
filters = {},
|
||||
hash = '',
|
||||
standalone = false,
|
||||
standalone?: number | null,
|
||||
) {
|
||||
const newSearchParams = new URLSearchParams();
|
||||
|
||||
// convert flattened { [id_column]: values } object
|
||||
// to nested filter object
|
||||
const obj = serializeActiveFilterValues(filters);
|
||||
const preselectFilters = encodeURIComponent(JSON.stringify(obj));
|
||||
newSearchParams.set(
|
||||
URL_PARAMS.preselectFilters,
|
||||
JSON.stringify(serializeActiveFilterValues(filters)),
|
||||
);
|
||||
|
||||
if (standalone) {
|
||||
newSearchParams.set(URL_PARAMS.standalone, standalone.toString());
|
||||
}
|
||||
|
||||
const hashSection = hash ? `#${hash}` : '';
|
||||
const standaloneParam = standalone ? '&standalone=true' : '';
|
||||
return `${pathname}?preselect_filters=${preselectFilters}${standaloneParam}${hashSection}`;
|
||||
|
||||
return `${pathname}?${newSearchParams.toString()}${hashSection}`;
|
||||
}
|
||||
|
|
@ -23,7 +23,8 @@ import { t } from '@superset-ui/core';
|
|||
import Popover from 'src/common/components/Popover';
|
||||
import FormLabel from 'src/components/FormLabel';
|
||||
import CopyToClipboard from 'src/components/CopyToClipboard';
|
||||
import { getShortUrl } from 'src/utils/common';
|
||||
import { getShortUrl } from 'src/utils/urlUtils';
|
||||
import { URL_PARAMS } from 'src/constants';
|
||||
import { getExploreLongUrl, getURIDirectory } from '../exploreUtils';
|
||||
|
||||
const propTypes = {
|
||||
|
|
@ -66,7 +67,7 @@ export default class EmbedCodeButton extends React.Component {
|
|||
generateEmbedHTML() {
|
||||
const srcLink = `${window.location.origin + getURIDirectory()}?r=${
|
||||
this.state.shortUrlId
|
||||
}&standalone=true&height=${this.state.height}`;
|
||||
}&${URL_PARAMS.standalone}=1&height=${this.state.height}`;
|
||||
return (
|
||||
'<iframe\n' +
|
||||
` width="${this.state.width}"\n` +
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ const propTypes = {
|
|||
table_name: PropTypes.string,
|
||||
vizType: PropTypes.string.isRequired,
|
||||
form_data: PropTypes.object,
|
||||
standalone: PropTypes.bool,
|
||||
standalone: PropTypes.number,
|
||||
timeout: PropTypes.number,
|
||||
refreshOverlayVisible: PropTypes.bool,
|
||||
chart: chartPropShape,
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ import {
|
|||
getFromLocalStorage,
|
||||
setInLocalStorage,
|
||||
} from 'src/utils/localStorageHelpers';
|
||||
import { URL_PARAMS } from 'src/constants';
|
||||
import ExploreChartPanel from './ExploreChartPanel';
|
||||
import ConnectedControlPanelsContainer from './ControlPanelsContainer';
|
||||
import SaveModal from './SaveModal';
|
||||
|
|
@ -67,7 +68,7 @@ const propTypes = {
|
|||
controls: PropTypes.object.isRequired,
|
||||
forcedHeight: PropTypes.string,
|
||||
form_data: PropTypes.object.isRequired,
|
||||
standalone: PropTypes.bool.isRequired,
|
||||
standalone: PropTypes.number.isRequired,
|
||||
timeout: PropTypes.number,
|
||||
impressionId: PropTypes.string,
|
||||
vizType: PropTypes.string,
|
||||
|
|
@ -187,7 +188,7 @@ function ExploreViewContainer(props) {
|
|||
const payload = { ...props.form_data };
|
||||
const longUrl = getExploreLongUrl(
|
||||
props.form_data,
|
||||
props.standalone ? 'standalone' : null,
|
||||
props.standalone ? URL_PARAMS.standalone : null,
|
||||
false,
|
||||
);
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -27,7 +27,9 @@ import {
|
|||
} from '@superset-ui/core';
|
||||
import { availableDomains } from 'src/utils/hostNamesConfig';
|
||||
import { safeStringify } from 'src/utils/safeStringify';
|
||||
import { URL_PARAMS } from 'src/constants';
|
||||
import { MULTI_OPERATORS } from './constants';
|
||||
import { DashboardStandaloneMode } from '../dashboard/util/constants';
|
||||
|
||||
const MAX_URL_LENGTH = 8000;
|
||||
|
||||
|
|
@ -99,8 +101,8 @@ export function getExploreLongUrl(
|
|||
search[key] = extraSearch[key];
|
||||
});
|
||||
search.form_data = safeStringify(formData);
|
||||
if (endpointType === 'standalone') {
|
||||
search.standalone = 'true';
|
||||
if (endpointType === URL_PARAMS.standalone) {
|
||||
search.standalone = DashboardStandaloneMode.HIDE_NAV;
|
||||
}
|
||||
const url = uri.directory(directory).search(search).toString();
|
||||
if (!allowOverflow && url.length > MAX_URL_LENGTH) {
|
||||
|
|
@ -172,8 +174,8 @@ export function getExploreUrl({
|
|||
if (endpointType === 'csv') {
|
||||
search.csv = 'true';
|
||||
}
|
||||
if (endpointType === 'standalone') {
|
||||
search.standalone = 'true';
|
||||
if (endpointType === URL_PARAMS.standalone) {
|
||||
search.standalone = '1';
|
||||
}
|
||||
if (endpointType === 'query') {
|
||||
search.query = 'true';
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ import {
|
|||
getTimeFormatter,
|
||||
TimeFormats,
|
||||
} from '@superset-ui/core';
|
||||
import { getClientErrorObject } from './getClientErrorObject';
|
||||
|
||||
// ATTENTION: If you change any constants, make sure to also change constants.py
|
||||
|
||||
|
|
@ -55,33 +54,6 @@ export function storeQuery(query) {
|
|||
});
|
||||
}
|
||||
|
||||
export function getParamsFromUrl() {
|
||||
const hash = window.location.search;
|
||||
const params = hash.split('?')[1].split('&');
|
||||
const newParams = {};
|
||||
params.forEach(p => {
|
||||
const value = p.split('=')[1].replace(/\+/g, ' ');
|
||||
const key = p.split('=')[0];
|
||||
newParams[key] = value;
|
||||
});
|
||||
return newParams;
|
||||
}
|
||||
|
||||
export function getShortUrl(longUrl) {
|
||||
return SupersetClient.post({
|
||||
endpoint: '/r/shortner/',
|
||||
postPayload: { data: `/${longUrl}` }, // note: url should contain 2x '/' to redirect properly
|
||||
parseMethod: 'text',
|
||||
stringify: false, // the url saves with an extra set of string quotes without this
|
||||
})
|
||||
.then(({ text }) => text)
|
||||
.catch(response =>
|
||||
getClientErrorObject(response).then(({ error, statusText }) =>
|
||||
Promise.reject(error || statusText),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
export function optionLabel(opt) {
|
||||
if (opt === null) {
|
||||
return NULL_STRING;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,63 @@
|
|||
/**
|
||||
* 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 { SupersetClient } from '@superset-ui/core';
|
||||
import { getClientErrorObject } from './getClientErrorObject';
|
||||
|
||||
export type UrlParamType = 'string' | 'number' | 'boolean';
|
||||
export function getUrlParam(paramName: string, type: 'string'): string;
|
||||
export function getUrlParam(paramName: string, type: 'number'): number;
|
||||
export function getUrlParam(paramName: string, type: 'boolean'): boolean;
|
||||
export function getUrlParam(paramName: string, type: UrlParamType): unknown {
|
||||
const urlParam = new URLSearchParams(window.location.search).get(paramName);
|
||||
switch (type) {
|
||||
case 'number':
|
||||
if (!urlParam) {
|
||||
return null;
|
||||
}
|
||||
if (urlParam === 'true') {
|
||||
return 1;
|
||||
}
|
||||
if (urlParam === 'false') {
|
||||
return 0;
|
||||
}
|
||||
if (!Number.isNaN(Number(urlParam))) {
|
||||
return Number(urlParam);
|
||||
}
|
||||
return null;
|
||||
// TODO: process other types when needed
|
||||
default:
|
||||
return urlParam;
|
||||
}
|
||||
}
|
||||
|
||||
export function getShortUrl(longUrl: string) {
|
||||
return SupersetClient.post({
|
||||
endpoint: '/r/shortner/',
|
||||
postPayload: { data: `/${longUrl}` }, // note: url should contain 2x '/' to redirect properly
|
||||
parseMethod: 'text',
|
||||
stringify: false, // the url saves with an extra set of string quotes without this
|
||||
})
|
||||
.then(({ text }) => text)
|
||||
.catch(response =>
|
||||
// @ts-ignore
|
||||
getClientErrorObject(response).then(({ error, statusText }) =>
|
||||
Promise.reject(error || statusText),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
@ -70,7 +70,7 @@ import sqlalchemy as sa
|
|||
from cryptography import x509
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.backends.openssl.x509 import _Certificate
|
||||
from flask import current_app, flash, g, Markup, render_template
|
||||
from flask import current_app, flash, g, Markup, render_template, request
|
||||
from flask_appbuilder import SQLA
|
||||
from flask_appbuilder.security.sqla.models import Role, User
|
||||
from flask_babel import gettext as __
|
||||
|
|
@ -254,6 +254,14 @@ class ReservedUrlParameters(str, Enum):
|
|||
STANDALONE = "standalone"
|
||||
EDIT_MODE = "edit"
|
||||
|
||||
@staticmethod
|
||||
def is_standalone_mode() -> Optional[bool]:
|
||||
standalone_param = request.args.get(ReservedUrlParameters.STANDALONE.value)
|
||||
standalone: Optional[bool] = (
|
||||
standalone_param and standalone_param != "false" and standalone_param != "0"
|
||||
)
|
||||
return standalone
|
||||
|
||||
|
||||
class RowLevelSecurityFilterType(str, Enum):
|
||||
REGULAR = "Regular"
|
||||
|
|
|
|||
|
|
@ -103,6 +103,7 @@ from superset.typing import FlaskResponse
|
|||
from superset.utils import core as utils
|
||||
from superset.utils.async_query_manager import AsyncQueryTokenException
|
||||
from superset.utils.cache import etag_cache
|
||||
from superset.utils.core import ReservedUrlParameters
|
||||
from superset.utils.dates import now_as_float
|
||||
from superset.utils.decorators import check_dashboard_access
|
||||
from superset.views.base import (
|
||||
|
|
@ -400,9 +401,10 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
|
|||
endpoint = "/superset/explore/?form_data={}".format(
|
||||
parse.quote(json.dumps({"slice_id": slice_id}))
|
||||
)
|
||||
param = utils.ReservedUrlParameters.STANDALONE.value
|
||||
if request.args.get(param) == "true":
|
||||
endpoint += f"&{param}=true"
|
||||
|
||||
is_standalone_mode = ReservedUrlParameters.is_standalone_mode()
|
||||
if is_standalone_mode:
|
||||
endpoint += f"&{ReservedUrlParameters.STANDALONE}={is_standalone_mode}"
|
||||
return redirect(endpoint)
|
||||
|
||||
def get_query_string_response(self, viz_obj: BaseViz) -> FlaskResponse:
|
||||
|
|
@ -783,10 +785,7 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
|
|||
datasource.type,
|
||||
datasource.name,
|
||||
)
|
||||
|
||||
standalone = (
|
||||
request.args.get(utils.ReservedUrlParameters.STANDALONE.value) == "true"
|
||||
)
|
||||
standalone_mode = ReservedUrlParameters.is_standalone_mode()
|
||||
dummy_datasource_data: Dict[str, Any] = {
|
||||
"type": datasource_type,
|
||||
"name": datasource_name,
|
||||
|
|
@ -802,7 +801,7 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
|
|||
"datasource_id": datasource_id,
|
||||
"datasource_type": datasource_type,
|
||||
"slice": slc.data if slc else None,
|
||||
"standalone": standalone,
|
||||
"standalone": standalone_mode,
|
||||
"user_id": user_id,
|
||||
"forced_height": request.args.get("height"),
|
||||
"common": common_bootstrap_payload(),
|
||||
|
|
@ -826,7 +825,7 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
|
|||
),
|
||||
entry="explore",
|
||||
title=title,
|
||||
standalone_mode=standalone,
|
||||
standalone_mode=standalone_mode,
|
||||
)
|
||||
|
||||
@api
|
||||
|
|
@ -1835,10 +1834,7 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
|
|||
superset_can_explore = security_manager.can_access("can_explore", "Superset")
|
||||
superset_can_csv = security_manager.can_access("can_csv", "Superset")
|
||||
slice_can_edit = security_manager.can_access("can_edit", "SliceModelView")
|
||||
|
||||
standalone_mode = (
|
||||
request.args.get(utils.ReservedUrlParameters.STANDALONE.value) == "true"
|
||||
)
|
||||
standalone_mode = ReservedUrlParameters.is_standalone_mode()
|
||||
edit_mode = (
|
||||
request.args.get(utils.ReservedUrlParameters.EDIT_MODE.value) == "true"
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in New Issue