mirror of
https://github.com/apache/superset.git
synced 2026-07-03 13:25:32 +00:00
Compare commits
39 Commits
fix-report
...
feat/toolt
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ffb26925a1 | ||
|
|
8750154a5e | ||
|
|
5c2a001270 | ||
|
|
15617a788c | ||
|
|
dc53a9038a | ||
|
|
ec57679ac6 | ||
|
|
9c72e3bf0e | ||
|
|
389a5d95dd | ||
|
|
a8a5c36142 | ||
|
|
d55789c6eb | ||
|
|
9c372e7a16 | ||
|
|
2a80689763 | ||
|
|
ac279ba873 | ||
|
|
47cacae15c | ||
|
|
d56dbc28aa | ||
|
|
d1eb412d06 | ||
|
|
3fc0dec9d5 | ||
|
|
f5283e62d4 | ||
|
|
0d5017d9ba | ||
|
|
4683485e2d | ||
|
|
102f543792 | ||
|
|
d77b76e9a5 | ||
|
|
2891c75cc3 | ||
|
|
3565111528 | ||
|
|
ff999e0fdd | ||
|
|
6cbce682bc | ||
|
|
3dccaf107d | ||
|
|
80244a0802 | ||
|
|
64770df289 | ||
|
|
5756e74cf8 | ||
|
|
93eafd7db7 | ||
|
|
db01f04bac | ||
|
|
b7e87ab1aa | ||
|
|
e3fd203183 | ||
|
|
95ad5e1be9 | ||
|
|
4a65f34402 | ||
|
|
a411dbba9a | ||
|
|
6caa5a89a4 | ||
|
|
0a506ba76a |
122
docs/docs/glossary.mdx
Normal file
122
docs/docs/glossary.mdx
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
---
|
||||||
|
title: Glossary
|
||||||
|
hide_title: true
|
||||||
|
sidebar_position: 10
|
||||||
|
---
|
||||||
|
|
||||||
|
import { getAllGlossaryTopics } from '../../superset-frontend/packages/superset-ui-core/src/glossary';
|
||||||
|
import { Table, ConfigProvider, theme } from 'antd';
|
||||||
|
import { useColorMode } from '@docusaurus/theme-common';
|
||||||
|
import { useCallback, useEffect, useRef } from 'react';
|
||||||
|
|
||||||
|
export const GlossaryStructure = [
|
||||||
|
{
|
||||||
|
title: 'Term',
|
||||||
|
dataIndex: 'title',
|
||||||
|
key: 'title',
|
||||||
|
width: 200,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Short Description',
|
||||||
|
dataIndex: 'short',
|
||||||
|
key: 'short',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const GlossaryContent = () => {
|
||||||
|
const { colorMode } = useColorMode();
|
||||||
|
const isDark = colorMode === 'dark';
|
||||||
|
const tableRefs = useRef({});
|
||||||
|
|
||||||
|
const scrollToRow = useCallback((topic, rowKey) => {
|
||||||
|
const topicId = encodeURIComponent(topic);
|
||||||
|
const encRowKey = encodeURIComponent(rowKey);
|
||||||
|
const row = tableRefs.current[topicId]?.[encRowKey];
|
||||||
|
if (row) {
|
||||||
|
row.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||||
|
row.classList.add('table-row-highlight');
|
||||||
|
setTimeout(() => row.classList.remove('table-row-highlight'), 2000);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let hash = '';
|
||||||
|
try {
|
||||||
|
hash = decodeURIComponent(window.location.hash.slice(1));
|
||||||
|
} catch (e) {
|
||||||
|
// Malformed percent-encoding in the URL hash — silently skip the
|
||||||
|
// scroll-to-row behavior rather than letting the page render fail.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!hash) return;
|
||||||
|
|
||||||
|
const [topic, term] = hash.split('__');
|
||||||
|
if (topic && term) scrollToRow(topic, hash);
|
||||||
|
}, [scrollToRow]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<ConfigProvider
|
||||||
|
theme={{
|
||||||
|
algorithm: isDark ? theme.darkAlgorithm : theme.defaultAlgorithm,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{getAllGlossaryTopics().map((topic) => {
|
||||||
|
const topicName = topic.getName();
|
||||||
|
const topicFragment = encodeURIComponent(topicName);
|
||||||
|
const terms = topic.getAllTerms();
|
||||||
|
return (
|
||||||
|
<div key={topicName} id={topicFragment}>
|
||||||
|
<h3>{topic.getDisplayName()}</h3>
|
||||||
|
<Table
|
||||||
|
dataSource={terms
|
||||||
|
.map((term) => {
|
||||||
|
const key = term.getTitle()
|
||||||
|
? encodeURIComponent(`${topicName}__${term.getTitle()}`)
|
||||||
|
: undefined;
|
||||||
|
return key
|
||||||
|
? {
|
||||||
|
title: term.getDisplayTitle(),
|
||||||
|
short: term.getShort(),
|
||||||
|
key,
|
||||||
|
}
|
||||||
|
: null;
|
||||||
|
})
|
||||||
|
.filter(Boolean)}
|
||||||
|
columns={GlossaryStructure}
|
||||||
|
rowKey="key"
|
||||||
|
pagination={false}
|
||||||
|
showHeader
|
||||||
|
bordered
|
||||||
|
onRow={(record) => {
|
||||||
|
if (!record?.key) return {};
|
||||||
|
const topicId = topicFragment;
|
||||||
|
|
||||||
|
return {
|
||||||
|
ref: (node) => {
|
||||||
|
if (!tableRefs.current[topicId]) tableRefs.current[topicId] = {};
|
||||||
|
if (node) {
|
||||||
|
tableRefs.current[topicId][record.key] = node;
|
||||||
|
} else {
|
||||||
|
// cleanup stale reference when row unmounts
|
||||||
|
delete tableRefs.current[topicId][record.key];
|
||||||
|
if (Object.keys(tableRefs.current[topicId]).length === 0) {
|
||||||
|
delete tableRefs.current[topicId];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ConfigProvider>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
## Glossary
|
||||||
|
|
||||||
|
<GlossaryContent />
|
||||||
@@ -60,6 +60,11 @@ const sidebars = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: 'doc',
|
||||||
|
label: 'Glossary',
|
||||||
|
id: 'glossary'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
type: 'doc',
|
type: 'doc',
|
||||||
label: 'FAQ',
|
label: 'FAQ',
|
||||||
|
|||||||
@@ -23,6 +23,10 @@ import { ControlSubSectionHeader } from '../components/ControlSubSectionHeader';
|
|||||||
import { ControlPanelSectionConfig } from '../types';
|
import { ControlPanelSectionConfig } from '../types';
|
||||||
import { formatSelectOptions, displayTimeRelatedControls } from '../utils';
|
import { formatSelectOptions, displayTimeRelatedControls } from '../utils';
|
||||||
|
|
||||||
|
import { glossary } from '@superset-ui/core';
|
||||||
|
|
||||||
|
const TIME_SHIFT_DESCRIPTION = glossary.Advanced_Analytics.Time_Shift.encode();
|
||||||
|
|
||||||
export const advancedAnalyticsControls: ControlPanelSectionConfig = {
|
export const advancedAnalyticsControls: ControlPanelSectionConfig = {
|
||||||
label: t('Advanced analytics'),
|
label: t('Advanced analytics'),
|
||||||
tabOverride: 'data',
|
tabOverride: 'data',
|
||||||
@@ -123,12 +127,7 @@ export const advancedAnalyticsControls: ControlPanelSectionConfig = {
|
|||||||
['156 weeks ago', t('156 weeks ago')],
|
['156 weeks ago', t('156 weeks ago')],
|
||||||
['3 years ago', t('3 years ago')],
|
['3 years ago', t('3 years ago')],
|
||||||
],
|
],
|
||||||
description: t(
|
description: TIME_SHIFT_DESCRIPTION,
|
||||||
'Overlay one or more timeseries from a ' +
|
|
||||||
'relative time period. Expects relative time deltas ' +
|
|
||||||
'in natural language (example: 24 hours, 7 days, ' +
|
|
||||||
'52 weeks, 365 days). Free text is supported.',
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -25,6 +25,10 @@ import {
|
|||||||
ControlState,
|
ControlState,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
import { INVALID_DATE } from '..';
|
import { INVALID_DATE } from '..';
|
||||||
|
import { glossary } from '@superset-ui/core';
|
||||||
|
|
||||||
|
// Glossary terms used for tooltips
|
||||||
|
const TIME_SHIFT_DESCRIPTION = glossary.Advanced_Analytics.Time_Shift.encode();
|
||||||
|
|
||||||
const fullChoices = [
|
const fullChoices = [
|
||||||
['1 day ago', t('1 day ago')],
|
['1 day ago', t('1 day ago')],
|
||||||
@@ -82,16 +86,7 @@ export const timeComparisonControls: ({
|
|||||||
placeholder: t('Select or type a custom value...'),
|
placeholder: t('Select or type a custom value...'),
|
||||||
label: t('Time shift'),
|
label: t('Time shift'),
|
||||||
choices: showFullChoices ? fullChoices : reducedChoices,
|
choices: showFullChoices ? fullChoices : reducedChoices,
|
||||||
description: t(
|
description: TIME_SHIFT_DESCRIPTION,
|
||||||
'Overlay results from a relative time period. ' +
|
|
||||||
'Expects relative time deltas ' +
|
|
||||||
'in natural language (example: 24 hours, 7 days, ' +
|
|
||||||
'52 weeks, 365 days). Free text is supported. ' +
|
|
||||||
'Use "Inherit range from time filters" ' +
|
|
||||||
'to shift the comparison time range ' +
|
|
||||||
'by the same length as your time range ' +
|
|
||||||
'and use "Custom" to set a custom comparison range.',
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -39,6 +39,13 @@ import {
|
|||||||
xAxisMixin,
|
xAxisMixin,
|
||||||
} from '..';
|
} from '..';
|
||||||
|
|
||||||
|
import { glossary } from '@superset-ui/core';
|
||||||
|
|
||||||
|
// Glossary terms used for tooltips
|
||||||
|
const DIMENSION_DESCRIPTION = glossary.Query.Dimension.encode();
|
||||||
|
const METRIC_DESCRIPTION = glossary.Query.Metric.encode();
|
||||||
|
const SORT_DESCRIPTION = glossary.Query.Sort.encode();
|
||||||
|
|
||||||
type Control = {
|
type Control = {
|
||||||
savedMetrics?: Metric[] | null;
|
savedMetrics?: Metric[] | null;
|
||||||
default?: unknown;
|
default?: unknown;
|
||||||
@@ -78,11 +85,7 @@ export const dndGroupByControl: SharedControlConfig<
|
|||||||
clearable: true,
|
clearable: true,
|
||||||
default: [],
|
default: [],
|
||||||
includeTime: false,
|
includeTime: false,
|
||||||
description: t(
|
description: DIMENSION_DESCRIPTION,
|
||||||
'Dimensions contain qualitative values such as names, dates, or geographical data. ' +
|
|
||||||
'Use dimensions to categorize, segment, and reveal the details in your data. ' +
|
|
||||||
'Dimensions affect the level of detail in the view.',
|
|
||||||
),
|
|
||||||
optionRenderer: (c: ColumnMeta) => <ColumnOption showType column={c} />,
|
optionRenderer: (c: ColumnMeta) => <ColumnOption showType column={c} />,
|
||||||
valueRenderer: (c: ColumnMeta) => <ColumnOption column={c} />,
|
valueRenderer: (c: ColumnMeta) => <ColumnOption column={c} />,
|
||||||
valueKey: 'column_name',
|
valueKey: 'column_name',
|
||||||
@@ -180,11 +183,7 @@ export const dndAdhocMetricsControl: SharedControlConfig<
|
|||||||
datasource,
|
datasource,
|
||||||
datasourceType: datasource?.type,
|
datasourceType: datasource?.type,
|
||||||
}),
|
}),
|
||||||
description: t(
|
description: METRIC_DESCRIPTION,
|
||||||
'Select one or many metrics to display. ' +
|
|
||||||
'You can use an aggregation function on a column ' +
|
|
||||||
'or write custom SQL to create a metric.',
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const dndAdhocMetricControl: typeof dndAdhocMetricsControl = {
|
export const dndAdhocMetricControl: typeof dndAdhocMetricsControl = {
|
||||||
@@ -224,11 +223,7 @@ export const dndSortByControl: SharedControlConfig<
|
|||||||
type: 'DndMetricSelect',
|
type: 'DndMetricSelect',
|
||||||
label: t('Sort query by'),
|
label: t('Sort query by'),
|
||||||
default: null,
|
default: null,
|
||||||
description: t(
|
description: SORT_DESCRIPTION,
|
||||||
'Orders the query result that generates the source data for this chart. ' +
|
|
||||||
'If a series or row limit is reached, this determines what data are truncated. ' +
|
|
||||||
'If undefined, defaults to the first metric (where appropriate).',
|
|
||||||
),
|
|
||||||
mapStateToProps: ({ datasource }) => ({
|
mapStateToProps: ({ datasource }) => ({
|
||||||
columns: datasource?.columns || [],
|
columns: datasource?.columns || [],
|
||||||
savedMetrics: defineSavedMetrics(datasource),
|
savedMetrics: defineSavedMetrics(datasource),
|
||||||
|
|||||||
@@ -86,6 +86,10 @@ import {
|
|||||||
dndTooltipMetricsControl,
|
dndTooltipMetricsControl,
|
||||||
} from './dndControls';
|
} from './dndControls';
|
||||||
import { matrixifyControls } from './matrixifyControls';
|
import { matrixifyControls } from './matrixifyControls';
|
||||||
|
import { glossary } from '@superset-ui/core';
|
||||||
|
|
||||||
|
const SERIES_DESCRIPTION = glossary.Query.Series.encode();
|
||||||
|
const ROW_LIMIT_DESCRIPTION = glossary.Query.Row_Limit.encode();
|
||||||
|
|
||||||
const categoricalSchemeRegistry = getCategoricalSchemeRegistry();
|
const categoricalSchemeRegistry = getCategoricalSchemeRegistry();
|
||||||
const sequentialSchemeRegistry = getSequentialSchemeRegistry();
|
const sequentialSchemeRegistry = getSequentialSchemeRegistry();
|
||||||
@@ -235,9 +239,7 @@ const row_limit: SharedControlConfig<'SelectControl'> = {
|
|||||||
],
|
],
|
||||||
default: 10000,
|
default: 10000,
|
||||||
choices: formatSelectOptions(ROW_LIMIT_OPTIONS),
|
choices: formatSelectOptions(ROW_LIMIT_OPTIONS),
|
||||||
description: t(
|
description: ROW_LIMIT_DESCRIPTION,
|
||||||
'Limits the number of the rows that are computed in the query that is the source of the data used for this chart.',
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const order_desc: SharedControlConfig<'CheckboxControl'> = {
|
const order_desc: SharedControlConfig<'CheckboxControl'> = {
|
||||||
@@ -262,12 +264,7 @@ const limit: SharedControlConfig<'SelectControl'> = {
|
|||||||
validators: [legacyValidateInteger],
|
validators: [legacyValidateInteger],
|
||||||
choices: formatSelectOptions(SERIES_LIMITS),
|
choices: formatSelectOptions(SERIES_LIMITS),
|
||||||
clearable: true,
|
clearable: true,
|
||||||
description: t(
|
description: SERIES_DESCRIPTION,
|
||||||
'Limits the number of series that get displayed. A joined subquery (or an extra phase ' +
|
|
||||||
'where subqueries are not supported) is applied to limit the number of series that get ' +
|
|
||||||
'fetched and rendered. This feature is useful when grouping by high cardinality ' +
|
|
||||||
'column(s) though does increase the query complexity and cost.',
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const series_limit: SharedControlConfig<'SelectControl'> = {
|
const series_limit: SharedControlConfig<'SelectControl'> = {
|
||||||
@@ -277,12 +274,7 @@ const series_limit: SharedControlConfig<'SelectControl'> = {
|
|||||||
placeholder: t('None'),
|
placeholder: t('None'),
|
||||||
validators: [legacyValidateInteger],
|
validators: [legacyValidateInteger],
|
||||||
choices: formatSelectOptions(SERIES_LIMITS),
|
choices: formatSelectOptions(SERIES_LIMITS),
|
||||||
description: t(
|
description: SERIES_DESCRIPTION,
|
||||||
'Limits the number of series that get displayed. A joined subquery (or an extra phase ' +
|
|
||||||
'where subqueries are not supported) is applied to limit the number of series that get ' +
|
|
||||||
'fetched and rendered. This feature is useful when grouping by high cardinality ' +
|
|
||||||
'column(s) though does increase the query complexity and cost.',
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const group_others_when_limit_reached: SharedControlConfig<'CheckboxControl'> =
|
const group_others_when_limit_reached: SharedControlConfig<'CheckboxControl'> =
|
||||||
|
|||||||
@@ -16,17 +16,70 @@
|
|||||||
* specific language governing permissions and limitations
|
* specific language governing permissions and limitations
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
|
import type { CSSProperties } from 'react';
|
||||||
import { Tooltip as AntdTooltip } from 'antd';
|
import { Tooltip as AntdTooltip } from 'antd';
|
||||||
|
|
||||||
import type { TooltipProps, TooltipPlacement } from './types';
|
import type { TooltipProps, TooltipPlacement } from './types';
|
||||||
|
import { resolveGlossaryString } from '@superset-ui/core';
|
||||||
|
|
||||||
|
const TOOLTIP_SEPARATOR_STYLE: CSSProperties = {
|
||||||
|
margin: '8px 0',
|
||||||
|
border: 'none',
|
||||||
|
borderTop: '1px solid rgba(255, 255, 255, 0.2)',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Tooltip = ({
|
||||||
|
overlayStyle,
|
||||||
|
title,
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}: TooltipProps) => {
|
||||||
|
if (typeof title !== 'string') {
|
||||||
|
return (
|
||||||
|
<AntdTooltip
|
||||||
|
title={title}
|
||||||
|
styles={{
|
||||||
|
body: { overflow: 'hidden', textOverflow: 'ellipsis' },
|
||||||
|
root: overlayStyle ?? {},
|
||||||
|
}}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</AntdTooltip>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const [glossaryUrl, description] = resolveGlossaryString(title);
|
||||||
|
const wrappedChildren = glossaryUrl ? (
|
||||||
|
<a href={glossaryUrl} target="_blank" rel="noopener noreferrer">
|
||||||
|
{children}
|
||||||
|
</a>
|
||||||
|
) : (
|
||||||
|
children
|
||||||
|
);
|
||||||
|
|
||||||
|
const wrappedDescription = glossaryUrl ? (
|
||||||
|
<>
|
||||||
|
{description}
|
||||||
|
<hr style={TOOLTIP_SEPARATOR_STYLE} />
|
||||||
|
<em>Click to Learn More</em>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
description
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AntdTooltip
|
||||||
|
title={wrappedDescription}
|
||||||
|
styles={{
|
||||||
|
body: { overflow: 'hidden', textOverflow: 'ellipsis' },
|
||||||
|
root: overlayStyle ?? {},
|
||||||
|
}}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{wrappedChildren}
|
||||||
|
</AntdTooltip>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const Tooltip = ({ overlayStyle, ...props }: TooltipProps) => (
|
|
||||||
<AntdTooltip
|
|
||||||
styles={{
|
|
||||||
body: { overflow: 'hidden', textOverflow: 'ellipsis' },
|
|
||||||
root: overlayStyle ?? {},
|
|
||||||
}}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
export type { TooltipProps, TooltipPlacement };
|
export type { TooltipProps, TooltipPlacement };
|
||||||
|
|||||||
@@ -0,0 +1,121 @@
|
|||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Glossary definition containing terms organized by topic.
|
||||||
|
*
|
||||||
|
* ## How to add new glossary entries:
|
||||||
|
*
|
||||||
|
* 1. Add a new topic (if needed) or use an existing one
|
||||||
|
* 2. Add a term under the topic with a key (term name) and value object containing:
|
||||||
|
* - short: A brief description (displayed in tooltips)
|
||||||
|
* - extended (optional): An extended description (displayed in documentation)
|
||||||
|
*
|
||||||
|
* ## Example:
|
||||||
|
* export const glossaryDefinition: GlossaryDefinition = {
|
||||||
|
* Query: {
|
||||||
|
* Row_Limit: {
|
||||||
|
* short: noTranslate('Limits the number of rows...'),
|
||||||
|
* extended: noTranslate('Additional details...'), // optional
|
||||||
|
* },
|
||||||
|
* },
|
||||||
|
* };
|
||||||
|
*
|
||||||
|
* ## Formatting Notes:
|
||||||
|
* - Term names with underscores (e.g., `Row_Limit`) will be displayed with spaces
|
||||||
|
* (e.g., "Row Limit") when rendered in the UI and documentation
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const glossaryDefinition: GlossaryDefinition = {
|
||||||
|
Query: {
|
||||||
|
Dimension: {
|
||||||
|
short: noTranslate(
|
||||||
|
'Dimensions contain qualitative values such as names, dates, or geographical data. ' +
|
||||||
|
'Use dimensions to categorize, segment, and reveal the details in your data. ' +
|
||||||
|
'Dimensions affect the level of detail in the view.',
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Metric: {
|
||||||
|
short: noTranslate(
|
||||||
|
'Select one or many metrics to display. ' +
|
||||||
|
'You can use an aggregation function on a column or write custom SQL to create a metric.',
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Series: {
|
||||||
|
short: noTranslate(
|
||||||
|
'Limits the number of series that get displayed. ' +
|
||||||
|
'A joined subquery (or an extra phase where subqueries are not supported) is applied ' +
|
||||||
|
'to limit the number of series that get fetched and rendered. ' +
|
||||||
|
'This feature is useful when grouping by high cardinality column(s) ' +
|
||||||
|
'though does increase the query complexity and cost.',
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Row_Limit: {
|
||||||
|
short: noTranslate(
|
||||||
|
'Limits the number of rows that get displayed. ' +
|
||||||
|
'This feature is useful when grouping by high cardinality column(s) ' +
|
||||||
|
'though does increase the query complexity and cost.',
|
||||||
|
),
|
||||||
|
},
|
||||||
|
Sort: {
|
||||||
|
short: noTranslate(
|
||||||
|
'Orders the query result that generates the source data for this chart. ' +
|
||||||
|
'If a series or row limit is reached, this determines what data are truncated. ' +
|
||||||
|
'If undefined, defaults to the first metric (where appropriate).',
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Advanced_Analytics: {
|
||||||
|
Time_Shift: {
|
||||||
|
short: noTranslate(
|
||||||
|
'Overlay results from a relative time period. ' +
|
||||||
|
'Expects relative time deltas in natural language (example: 24 hours, 7 days, ' +
|
||||||
|
'52 weeks, 365 days). Free text is supported. ' +
|
||||||
|
'Use "Inherit range from time filters" to shift the comparison time range ' +
|
||||||
|
'by the same length as your time range and use "Custom" to set a custom comparison range.',
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identity passthrough used in environments (such as the docs site) that do
|
||||||
|
* not have an i18n runtime. Translation of glossary strings is performed at
|
||||||
|
* resolution time by callers in app contexts that do have i18n available.
|
||||||
|
*
|
||||||
|
* Named `noTranslate` (rather than `t`) so it does not visually shadow the
|
||||||
|
* imported i18n `t` used elsewhere in this package.
|
||||||
|
*/
|
||||||
|
function noTranslate(message: string): string {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The glossary definition is a nested object where the first level keys are topics,
|
||||||
|
* and the second level keys are term titles. This remains a static string-based
|
||||||
|
* structure, mainly for good IDE autocomplete.
|
||||||
|
*/
|
||||||
|
export type GlossaryStrings = {
|
||||||
|
short: string;
|
||||||
|
extended?: string;
|
||||||
|
};
|
||||||
|
export type GlossaryDefinition = Record<
|
||||||
|
string,
|
||||||
|
Record<string, GlossaryStrings>
|
||||||
|
>;
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Local type definition to avoid circular dependency with glossaryUtils
|
||||||
|
type Glossary = Record<string, Record<string, GlossaryTerm>>;
|
||||||
|
|
||||||
|
// Encoding format prefix for glossary strings
|
||||||
|
export const GLOSSARY_ENCODING_PREFIX = '[GLOSSARY]|';
|
||||||
|
|
||||||
|
export class GlossaryTerm {
|
||||||
|
/**
|
||||||
|
* The topic under which the term is categorized.
|
||||||
|
*/
|
||||||
|
private readonly topic: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the term being defined.
|
||||||
|
*/
|
||||||
|
private readonly title: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A short description of the term. Displayed on the frontend as a tooltip.
|
||||||
|
*/
|
||||||
|
private readonly short: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An extended description of the term, shown alongside short on the documentation.
|
||||||
|
*/
|
||||||
|
private readonly extended?: string;
|
||||||
|
|
||||||
|
constructor(options: {
|
||||||
|
topic: string;
|
||||||
|
title: string;
|
||||||
|
short: string;
|
||||||
|
extended?: string;
|
||||||
|
}) {
|
||||||
|
this.topic = options.topic;
|
||||||
|
this.title = options.title;
|
||||||
|
this.short = options.short;
|
||||||
|
this.extended = options.extended;
|
||||||
|
}
|
||||||
|
|
||||||
|
getTopic(): string {
|
||||||
|
return this.topic;
|
||||||
|
}
|
||||||
|
|
||||||
|
getTitle(): string {
|
||||||
|
return this.title;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a formatted display version of the title with underscores replaced by spaces.
|
||||||
|
*/
|
||||||
|
getDisplayTitle(): string {
|
||||||
|
return this.title.replace(/_/g, ' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the short description, optionally transformed by a provided translation function.
|
||||||
|
*/
|
||||||
|
getShort(t?: (value: string) => string): string {
|
||||||
|
if (!t) {
|
||||||
|
return this.short;
|
||||||
|
}
|
||||||
|
return t(this.short);
|
||||||
|
}
|
||||||
|
|
||||||
|
getExtended(t?: (value: string) => string): string | undefined {
|
||||||
|
if (!t) {
|
||||||
|
return this.extended;
|
||||||
|
}
|
||||||
|
if (!this.extended) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return t(this.extended);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes the glossary term into a string format that can be resolved later.
|
||||||
|
* Format: [GLOSSARY]|topic|title
|
||||||
|
*/
|
||||||
|
encode(): string {
|
||||||
|
return `${GLOSSARY_ENCODING_PREFIX}${this.topic}|${this.title}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GlossaryTopic {
|
||||||
|
private readonly name: string;
|
||||||
|
|
||||||
|
private readonly terms: Map<string, GlossaryTerm>;
|
||||||
|
|
||||||
|
constructor(name: string, terms: GlossaryTerm[]) {
|
||||||
|
this.name = name;
|
||||||
|
this.terms = new Map(terms.map(term => [term.getTitle(), term]));
|
||||||
|
}
|
||||||
|
|
||||||
|
getName(): string {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a formatted display version of the topic name with underscores replaced by spaces.
|
||||||
|
*/
|
||||||
|
getDisplayName(): string {
|
||||||
|
return this.name.replace(/_/g, ' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
getTerm(title: string): GlossaryTerm | undefined {
|
||||||
|
return this.terms.get(title);
|
||||||
|
}
|
||||||
|
|
||||||
|
getAllTerms(): GlossaryTerm[] {
|
||||||
|
return Array.from(this.terms.values());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GlossaryMap {
|
||||||
|
private readonly topics: Map<string, GlossaryTopic>;
|
||||||
|
|
||||||
|
constructor(glossary: Glossary) {
|
||||||
|
const topics = new Map<string, GlossaryTopic>();
|
||||||
|
|
||||||
|
Object.entries(glossary).forEach(([topicName, termsByTitle]) => {
|
||||||
|
const topicTerms = Object.values(termsByTitle);
|
||||||
|
topics.set(topicName, new GlossaryTopic(topicName, topicTerms));
|
||||||
|
});
|
||||||
|
|
||||||
|
this.topics = topics;
|
||||||
|
}
|
||||||
|
|
||||||
|
getTopic(topicName: string): GlossaryTopic | undefined {
|
||||||
|
return this.topics.get(topicName);
|
||||||
|
}
|
||||||
|
|
||||||
|
getAllTopics(): GlossaryTopic[] {
|
||||||
|
return Array.from(this.topics.values());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 {
|
||||||
|
GlossaryMap,
|
||||||
|
GlossaryTerm,
|
||||||
|
type GlossaryTopic,
|
||||||
|
} from './glossaryModels';
|
||||||
|
import { glossaryDefinition } from './glossary';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The exported glossary object is a runtime structure where each entry is a GlossaryTerm instance, but the key
|
||||||
|
* structure mirrors `glossaryDefinition` so IDEs can autocomplete, yet callers can use methods like `getShort()`.
|
||||||
|
*/
|
||||||
|
export type Glossary = {
|
||||||
|
[Topic in keyof typeof glossaryDefinition]: {
|
||||||
|
[Title in keyof (typeof glossaryDefinition)[Topic]]: GlossaryTerm;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const glossary: Glossary = Object.fromEntries(
|
||||||
|
Object.entries(glossaryDefinition).map(([topic, termsByTitle]) => [
|
||||||
|
topic,
|
||||||
|
Object.fromEntries(
|
||||||
|
Object.entries(termsByTitle).map(([title, termStrings]) => [
|
||||||
|
title,
|
||||||
|
new GlossaryTerm({
|
||||||
|
topic,
|
||||||
|
title,
|
||||||
|
short: termStrings.short,
|
||||||
|
extended: termStrings.extended ?? '',
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
) as Glossary;
|
||||||
|
|
||||||
|
const glossaryMap = new GlossaryMap(glossary);
|
||||||
|
|
||||||
|
export const getAllGlossaryTopics = (): GlossaryTopic[] =>
|
||||||
|
glossaryMap.getAllTopics();
|
||||||
|
|
||||||
|
export const getGlossaryTopic = (
|
||||||
|
topicName: string,
|
||||||
|
): GlossaryTopic | undefined => glossaryMap.getTopic(topicName);
|
||||||
|
|
||||||
|
export default glossary;
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export { GlossaryTerm, GlossaryTopic } from './glossaryModels';
|
||||||
|
export {
|
||||||
|
default as glossary,
|
||||||
|
getAllGlossaryTopics,
|
||||||
|
getGlossaryTopic,
|
||||||
|
} from './glossaryUtils';
|
||||||
|
export { resolveGlossaryString } from './tooltipUtils';
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
/**
|
||||||
|
* 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 { getGlossaryTopic } from './glossaryUtils';
|
||||||
|
import { t } from '@superset-ui/core';
|
||||||
|
|
||||||
|
export const GLOSSARY_BASE_URL = 'https://superset.apache.org/docs';
|
||||||
|
|
||||||
|
// Pattern matches: [GLOSSARY]|topic|title
|
||||||
|
// Captures: topic and title for lookup in glossary
|
||||||
|
const GLOSSARY_ENCODING_PATTERN = /^\[GLOSSARY\]\|([^|]+)\|([^|]+)$/;
|
||||||
|
|
||||||
|
export const resolveGlossaryString = (
|
||||||
|
glossaryString: string,
|
||||||
|
): [string | undefined, string] => {
|
||||||
|
const encoded = glossaryString.trim();
|
||||||
|
const match = encoded.match(GLOSSARY_ENCODING_PATTERN);
|
||||||
|
if (!match) {
|
||||||
|
return [undefined, encoded];
|
||||||
|
}
|
||||||
|
const topic = match[1];
|
||||||
|
const title = match[2];
|
||||||
|
|
||||||
|
// Look up the term from the glossary to get the translated description
|
||||||
|
const glossaryTopic = getGlossaryTopic(topic);
|
||||||
|
const term = glossaryTopic?.getTerm(title);
|
||||||
|
const description = term ? term.getShort(t) : encoded;
|
||||||
|
|
||||||
|
const glossaryUrl = buildGlossaryUrl(topic, title);
|
||||||
|
return [glossaryUrl, description];
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildGlossaryUrl = (topic: string, title: string): string =>
|
||||||
|
`${GLOSSARY_BASE_URL}/glossary#${encodeURIComponent(`${topic}__${title}`)}`;
|
||||||
@@ -35,3 +35,4 @@ export * from './ui-overrides';
|
|||||||
export * from './hooks';
|
export * from './hooks';
|
||||||
export * from './currency-format';
|
export * from './currency-format';
|
||||||
export * from './time-comparison';
|
export * from './time-comparison';
|
||||||
|
export * from './glossary';
|
||||||
|
|||||||
@@ -28,6 +28,9 @@ import {
|
|||||||
getStandardizedControls,
|
getStandardizedControls,
|
||||||
} from '@superset-ui/chart-controls';
|
} from '@superset-ui/chart-controls';
|
||||||
import OptionDescription from './OptionDescription';
|
import OptionDescription from './OptionDescription';
|
||||||
|
import { glossary } from '@superset-ui/core';
|
||||||
|
|
||||||
|
const TIME_SHIFT_DESCRIPTION = glossary.Advanced_Analytics.Time_Shift.encode();
|
||||||
|
|
||||||
const config: ControlPanelConfig = {
|
const config: ControlPanelConfig = {
|
||||||
controlPanelSections: [
|
controlPanelSections: [
|
||||||
@@ -321,12 +324,7 @@ const config: ControlPanelConfig = {
|
|||||||
['156 weeks', t('156 weeks')],
|
['156 weeks', t('156 weeks')],
|
||||||
['3 years', t('3 years')],
|
['3 years', t('3 years')],
|
||||||
],
|
],
|
||||||
description: t(
|
description: TIME_SHIFT_DESCRIPTION,
|
||||||
'Overlay one or more timeseries from a ' +
|
|
||||||
'relative time period. Expects relative time deltas ' +
|
|
||||||
'in natural language (example: 24 hours, 7 days, ' +
|
|
||||||
'52 weeks, 365 days). Free text is supported.',
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -26,6 +26,9 @@ import {
|
|||||||
sections,
|
sections,
|
||||||
getStandardizedControls,
|
getStandardizedControls,
|
||||||
} from '@superset-ui/chart-controls';
|
} from '@superset-ui/chart-controls';
|
||||||
|
import { glossary } from '@superset-ui/core';
|
||||||
|
|
||||||
|
const TIME_SHIFT_DESCRIPTION = glossary.Advanced_Analytics.Time_Shift.encode();
|
||||||
|
|
||||||
const config: ControlPanelConfig = {
|
const config: ControlPanelConfig = {
|
||||||
controlPanelSections: [
|
controlPanelSections: [
|
||||||
@@ -204,12 +207,7 @@ const config: ControlPanelConfig = {
|
|||||||
['156 weeks', t('156 weeks')],
|
['156 weeks', t('156 weeks')],
|
||||||
['3 years', t('3 years')],
|
['3 years', t('3 years')],
|
||||||
],
|
],
|
||||||
description: t(
|
description: TIME_SHIFT_DESCRIPTION,
|
||||||
'Overlay one or more timeseries from a ' +
|
|
||||||
'relative time period. Expects relative time deltas ' +
|
|
||||||
'in natural language (example: 24 hours, 7 days, ' +
|
|
||||||
'52 weeks, 365 days). Free text is supported.',
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -28,6 +28,10 @@ import {
|
|||||||
D3_FORMAT_OPTIONS,
|
D3_FORMAT_OPTIONS,
|
||||||
} from '@superset-ui/chart-controls';
|
} from '@superset-ui/chart-controls';
|
||||||
|
|
||||||
|
import { glossary } from '@superset-ui/core';
|
||||||
|
|
||||||
|
const TIME_SHIFT_DESCRIPTION = glossary.Advanced_Analytics.Time_Shift.encode();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Plugins in question:
|
Plugins in question:
|
||||||
|
|
||||||
@@ -472,12 +476,7 @@ export const timeSeriesSection: ControlPanelSectionConfig[] = [
|
|||||||
['156 weeks', t('156 weeks')],
|
['156 weeks', t('156 weeks')],
|
||||||
['3 years', t('3 years')],
|
['3 years', t('3 years')],
|
||||||
],
|
],
|
||||||
description: t(
|
description: TIME_SHIFT_DESCRIPTION,
|
||||||
'Overlay one or more timeseries from a ' +
|
|
||||||
'relative time period. Expects relative time deltas ' +
|
|
||||||
'in natural language (example: 24 hours, 7 days, ' +
|
|
||||||
'52 weeks, 365 days). Free text is supported.',
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import { t } from '@apache-superset/core/translation';
|
|||||||
import { css, useTheme, SupersetTheme } from '@apache-superset/core/theme';
|
import { css, useTheme, SupersetTheme } from '@apache-superset/core/theme';
|
||||||
import { FormLabel, InfoTooltip, Tooltip } from '@superset-ui/core/components';
|
import { FormLabel, InfoTooltip, Tooltip } from '@superset-ui/core/components';
|
||||||
import { Icons } from '@superset-ui/core/components/Icons';
|
import { Icons } from '@superset-ui/core/components/Icons';
|
||||||
|
|
||||||
type ValidationError = string;
|
type ValidationError = string;
|
||||||
|
|
||||||
export type ControlHeaderProps = {
|
export type ControlHeaderProps = {
|
||||||
@@ -93,15 +92,8 @@ const ControlHeader: FC<ControlHeaderProps> = ({
|
|||||||
>
|
>
|
||||||
{description && (
|
{description && (
|
||||||
<span>
|
<span>
|
||||||
<Tooltip
|
<Tooltip title={description}>
|
||||||
id="description-tooltip"
|
<Icons.InfoCircleOutlined css={iconStyles} />
|
||||||
title={description}
|
|
||||||
placement="top"
|
|
||||||
>
|
|
||||||
<Icons.InfoCircleOutlined
|
|
||||||
css={iconStyles}
|
|
||||||
onClick={tooltipOnClick}
|
|
||||||
/>
|
|
||||||
</Tooltip>{' '}
|
</Tooltip>{' '}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -22,6 +22,10 @@ import {
|
|||||||
ControlSubSectionHeader,
|
ControlSubSectionHeader,
|
||||||
} from '@superset-ui/chart-controls';
|
} from '@superset-ui/chart-controls';
|
||||||
|
|
||||||
|
import { glossary } from '@superset-ui/core';
|
||||||
|
|
||||||
|
const TIME_SHIFT_DESCRIPTION = glossary.Advanced_Analytics.Time_Shift.encode();
|
||||||
|
|
||||||
export const datasourceAndVizType: ControlPanelSectionConfig = {
|
export const datasourceAndVizType: ControlPanelSectionConfig = {
|
||||||
controlSetRows: [
|
controlSetRows: [
|
||||||
['datasource'],
|
['datasource'],
|
||||||
@@ -203,12 +207,7 @@ export const NVD3TimeSeries: ControlPanelSectionConfig[] = [
|
|||||||
['156 weeks', t('156 weeks')],
|
['156 weeks', t('156 weeks')],
|
||||||
['3 years', t('3 years')],
|
['3 years', t('3 years')],
|
||||||
],
|
],
|
||||||
description: t(
|
description: TIME_SHIFT_DESCRIPTION,
|
||||||
'Overlay one or more timeseries from a ' +
|
|
||||||
'relative time period. Expects relative time deltas ' +
|
|
||||||
'in natural language (example: 24 hours, 7 days, ' +
|
|
||||||
'52 weeks, 365 days). Free text is supported.',
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user