Merge branch 'data-manipulation-improvements' of github.com:grafana/grafana into data-manipulation-improvements
This commit is contained in:
@@ -1,229 +0,0 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { memo, useCallback, useRef } from 'react';
|
||||
|
||||
import { DataFrame, GrafanaTheme2, SelectableValue } from '@grafana/data';
|
||||
import { Trans } from '@grafana/i18n';
|
||||
import { SceneDataQuery, VizPanel } from '@grafana/scenes';
|
||||
import { Container, ScrollContainer, useStyles2 } from '@grafana/ui';
|
||||
import { isExpressionQuery } from 'app/features/expressions/guards';
|
||||
|
||||
import { DetailViewHeader } from './DetailViewHeader';
|
||||
import { ExpressionDetailView } from './ExpressionDetailView';
|
||||
import { PanelDataTransformationsTab, PanelDataTransformationsTabRendered } from './PanelDataTransformationsTab';
|
||||
import { QueryDetailView } from './QueryDetailView';
|
||||
import { QueryLibraryView, QueryLibraryViewRef } from './QueryLibraryView';
|
||||
import { TransformationPickerView } from './TransformationPickerView';
|
||||
import { TabId, QueryTransformItem } from './types';
|
||||
|
||||
export interface QueryLibraryMode {
|
||||
active: boolean;
|
||||
mode: 'browse' | 'save';
|
||||
currentQuery?: SceneDataQuery;
|
||||
}
|
||||
|
||||
interface DetailViewProps {
|
||||
selectedItem: QueryTransformItem | undefined;
|
||||
panel: VizPanel;
|
||||
tabs: Array<{ tabId: TabId }>;
|
||||
onRemoveTransform?: (index: number) => void;
|
||||
onToggleTransformVisibility?: (index: number) => void;
|
||||
isAddingTransform?: boolean;
|
||||
onAddTransformation?: (selectedItem: SelectableValue<string>, customOptions?: Record<string, unknown>) => void;
|
||||
onCancelAddTransform?: () => void;
|
||||
transformationData?: DataFrame[];
|
||||
onGoToQueries?: () => void;
|
||||
queryLibraryMode?: QueryLibraryMode;
|
||||
onQueryLibrarySelect?: (query: SceneDataQuery) => void;
|
||||
onQueryLibrarySave?: (name: string, description: string) => void;
|
||||
onQueryLibraryClose?: () => void;
|
||||
onOpenQueryLibrary?: (mode: 'browse' | 'save', index?: number) => void;
|
||||
onOpenQueryInspector?: () => void;
|
||||
isDebugMode?: boolean;
|
||||
debugPosition?: number;
|
||||
}
|
||||
|
||||
export const DetailView = memo(
|
||||
({
|
||||
selectedItem,
|
||||
panel,
|
||||
tabs,
|
||||
onRemoveTransform,
|
||||
onToggleTransformVisibility,
|
||||
isAddingTransform,
|
||||
onAddTransformation,
|
||||
onCancelAddTransform,
|
||||
transformationData,
|
||||
onGoToQueries,
|
||||
queryLibraryMode,
|
||||
onQueryLibrarySelect,
|
||||
onQueryLibrarySave,
|
||||
onQueryLibraryClose,
|
||||
onOpenQueryLibrary,
|
||||
onOpenQueryInspector,
|
||||
isDebugMode,
|
||||
debugPosition,
|
||||
}: DetailViewProps) => {
|
||||
const styles = useStyles2(getStyles);
|
||||
const queryLibraryRef = useRef<QueryLibraryViewRef>(null);
|
||||
|
||||
const handleSelectQueryFromHeader = useCallback(() => {
|
||||
queryLibraryRef.current?.selectCurrentQuery();
|
||||
}, []);
|
||||
|
||||
const handleSaveQueryFromHeader = useCallback(() => {
|
||||
queryLibraryRef.current?.saveQuery();
|
||||
}, []);
|
||||
|
||||
const renderContent = useCallback(() => {
|
||||
// Show transformation picker when in add mode
|
||||
if (isAddingTransform && onAddTransformation && onCancelAddTransform) {
|
||||
return (
|
||||
<TransformationPickerView
|
||||
data={transformationData || []}
|
||||
onAddTransformation={onAddTransformation}
|
||||
onCancel={onCancelAddTransform}
|
||||
onGoToQueries={onGoToQueries}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// Show QueryLibraryView when in query library mode
|
||||
if (queryLibraryMode?.active && onQueryLibraryClose) {
|
||||
return (
|
||||
<>
|
||||
<DetailViewHeader
|
||||
queryLibraryMode={queryLibraryMode.mode}
|
||||
onSelectQuery={queryLibraryMode.mode === 'browse' ? handleSelectQueryFromHeader : undefined}
|
||||
onSaveQuery={queryLibraryMode.mode === 'save' ? handleSaveQueryFromHeader : undefined}
|
||||
onClose={onQueryLibraryClose}
|
||||
/>
|
||||
<QueryLibraryView
|
||||
ref={queryLibraryRef}
|
||||
mode={queryLibraryMode.mode}
|
||||
currentQuery={queryLibraryMode.currentQuery}
|
||||
onSelectQuery={onQueryLibrarySelect}
|
||||
onSaveQuery={onQueryLibrarySave}
|
||||
onClose={onQueryLibraryClose}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (!selectedItem) {
|
||||
return (
|
||||
<div className={styles.emptyState}>
|
||||
<p>
|
||||
<Trans i18nKey="dashboard-scene.panel-data-pane.empty-state">
|
||||
Select a query or transformation to edit
|
||||
</Trans>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (selectedItem.type === 'query') {
|
||||
const query = selectedItem.data;
|
||||
return (
|
||||
<>
|
||||
<DetailViewHeader
|
||||
selectedItem={selectedItem}
|
||||
panel={panel}
|
||||
onOpenQueryLibrary={onOpenQueryLibrary}
|
||||
onOpenQueryInspector={onOpenQueryInspector}
|
||||
/>
|
||||
<ScrollContainer>
|
||||
<QueryDetailView panel={panel} query={query} queryIndex={selectedItem.index} />
|
||||
</ScrollContainer>
|
||||
</>
|
||||
);
|
||||
} else if (selectedItem.type === 'expression') {
|
||||
const data = selectedItem.data;
|
||||
if (isExpressionQuery(data)) {
|
||||
return (
|
||||
<>
|
||||
<DetailViewHeader selectedItem={selectedItem} panel={panel} onOpenQueryInspector={onOpenQueryInspector} />
|
||||
<ScrollContainer>
|
||||
<ExpressionDetailView panel={panel} expression={data} expressionIndex={selectedItem.index} />
|
||||
</ScrollContainer>
|
||||
</>
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const transformsTab = tabs.find((t): t is PanelDataTransformationsTab => t.tabId === TabId.Transformations);
|
||||
if (transformsTab) {
|
||||
return (
|
||||
<>
|
||||
<DetailViewHeader
|
||||
selectedItem={selectedItem}
|
||||
panel={panel}
|
||||
onRemoveTransform={onRemoveTransform}
|
||||
onToggleTransformVisibility={onToggleTransformVisibility}
|
||||
isDebugMode={isDebugMode}
|
||||
debugPosition={debugPosition}
|
||||
/>
|
||||
<ScrollContainer>
|
||||
<Container>
|
||||
<PanelDataTransformationsTabRendered model={transformsTab} selectedIdx={selectedItem.index} />
|
||||
</Container>
|
||||
</ScrollContainer>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}, [
|
||||
isAddingTransform,
|
||||
onAddTransformation,
|
||||
onCancelAddTransform,
|
||||
queryLibraryMode?.active,
|
||||
queryLibraryMode?.mode,
|
||||
queryLibraryMode?.currentQuery,
|
||||
onQueryLibraryClose,
|
||||
selectedItem,
|
||||
transformationData,
|
||||
onGoToQueries,
|
||||
handleSelectQueryFromHeader,
|
||||
handleSaveQueryFromHeader,
|
||||
onQueryLibrarySelect,
|
||||
onQueryLibrarySave,
|
||||
styles.emptyState,
|
||||
panel,
|
||||
onOpenQueryLibrary,
|
||||
onOpenQueryInspector,
|
||||
tabs,
|
||||
onRemoveTransform,
|
||||
onToggleTransformVisibility,
|
||||
isDebugMode,
|
||||
debugPosition,
|
||||
]);
|
||||
|
||||
return <div className={styles.container}>{renderContent()}</div>;
|
||||
}
|
||||
);
|
||||
|
||||
DetailView.displayName = 'DetailView';
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => {
|
||||
return {
|
||||
container: css({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
background: theme.colors.background.primary,
|
||||
border: `1px solid ${theme.colors.border.weak}`,
|
||||
borderTopLeftRadius: theme.shape.radius.md,
|
||||
borderTopRightRadius: theme.shape.radius.md,
|
||||
overflow: 'hidden',
|
||||
}),
|
||||
emptyState: css({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
height: '100%',
|
||||
color: theme.colors.text.secondary,
|
||||
fontSize: theme.typography.h5.fontSize,
|
||||
}),
|
||||
};
|
||||
};
|
||||
@@ -1,8 +1,6 @@
|
||||
import { css, cx } from '@emotion/css';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
import { DataTransformerConfig, GrafanaTheme2, SelectableValue } from '@grafana/data';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { DataTransformerConfig, SelectableValue } from '@grafana/data';
|
||||
import {
|
||||
SceneComponentProps,
|
||||
SceneDataQuery,
|
||||
@@ -13,7 +11,6 @@ import {
|
||||
SceneObjectUrlValues,
|
||||
VizPanel,
|
||||
} from '@grafana/scenes';
|
||||
import { useStyles2 } from '@grafana/ui';
|
||||
import { getConfig } from 'app/core/config';
|
||||
import { contextSrv } from 'app/core/services/context_srv';
|
||||
import { getRulesPermissions } from 'app/features/alerting/unified/utils/access-control';
|
||||
@@ -22,11 +19,10 @@ import { ExpressionQueryType } from 'app/features/expressions/types';
|
||||
|
||||
import { getQueryRunnerFor } from '../../utils/utils';
|
||||
|
||||
import { DetailView, QueryLibraryMode } from './DetailView';
|
||||
import { PanelDataAlertingTab } from './PanelDataAlertingTab';
|
||||
import { PanelDataQueriesTab } from './PanelDataQueriesTab';
|
||||
import { PanelDataSidebarRendered } from './PanelDataSidebar';
|
||||
import { PanelDataTransformationsTab } from './PanelDataTransformationsTab';
|
||||
import { QueryTransformDetailView, QueryLibraryMode } from './QueryTransformDetailView';
|
||||
import { useQueryTransformItems } from './hooks';
|
||||
import { PanelDataPaneTab, TabId } from './types';
|
||||
import { isDataTransformerConfig, queryItemId, transformItemId } from './utils';
|
||||
@@ -34,7 +30,6 @@ import { isDataTransformerConfig, queryItemId, transformItemId } from './utils';
|
||||
export interface PanelDataPaneState extends SceneObjectState {
|
||||
tabs: PanelDataPaneTab[];
|
||||
tab: TabId;
|
||||
sidebarCollapsed: boolean;
|
||||
selectedQueryTransform: string | null;
|
||||
panelRef: SceneObjectRef<VizPanel>;
|
||||
transformPickerIndex?: number | null;
|
||||
@@ -45,7 +40,6 @@ export interface PanelDataPaneState extends SceneObjectState {
|
||||
|
||||
export class PanelDataPane extends SceneObjectBase<PanelDataPaneState> {
|
||||
static Component = PanelDataPaneRendered;
|
||||
static FooterComponent = PanelDataSidebarRendered;
|
||||
|
||||
protected _urlSync = new SceneObjectUrlSyncConfig(this, { keys: ['tab', 'selectedQueryTransform'] });
|
||||
|
||||
@@ -67,7 +61,6 @@ export class PanelDataPane extends SceneObjectBase<PanelDataPaneState> {
|
||||
panelRef,
|
||||
tabs,
|
||||
tab,
|
||||
sidebarCollapsed: false,
|
||||
queryLibraryMode: {
|
||||
active: false,
|
||||
mode: 'browse',
|
||||
@@ -86,10 +79,6 @@ export class PanelDataPane extends SceneObjectBase<PanelDataPaneState> {
|
||||
this.setState({ selectedQueryTransform: selectedId });
|
||||
};
|
||||
|
||||
public onCollapseSidebar = (newState: boolean) => {
|
||||
this.setState({ sidebarCollapsed: newState });
|
||||
};
|
||||
|
||||
public onTransformPicker = (index?: number | null) => {
|
||||
this.setState({ transformPickerIndex: index });
|
||||
};
|
||||
@@ -130,7 +119,6 @@ function PanelDataPaneRendered({ model }: SceneComponentProps<PanelDataPane>) {
|
||||
isDebugMode = false,
|
||||
debugPosition = 0,
|
||||
} = model.useState();
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
// Subscribe to query runner and tab state changes
|
||||
const panel = panelRef.resolve();
|
||||
@@ -325,30 +313,26 @@ function PanelDataPaneRendered({ model }: SceneComponentProps<PanelDataPane>) {
|
||||
const series = sourceData?.data?.series || [];
|
||||
|
||||
return (
|
||||
<div className={styles.dataPane} data-testid={selectors.components.PanelEditor.DataPane.content}>
|
||||
<div className={cx(styles.unifiedLayout)}>
|
||||
<DetailView
|
||||
selectedItem={selectedItem}
|
||||
panel={panel}
|
||||
tabs={tabs}
|
||||
onRemoveTransform={handleRemoveTransform}
|
||||
onToggleTransformVisibility={handleToggleTransformVisibility}
|
||||
isAddingTransform={transformPickerIndex != null}
|
||||
onAddTransformation={handleAddTransform}
|
||||
onCancelAddTransform={() => model.onTransformPicker(null)}
|
||||
transformationData={series}
|
||||
onGoToQueries={handleGoToQueries}
|
||||
queryLibraryMode={queryLibraryMode}
|
||||
onQueryLibrarySelect={handleQueryLibrarySelect}
|
||||
onQueryLibrarySave={handleQueryLibrarySave}
|
||||
onQueryLibraryClose={handleQueryLibraryClose}
|
||||
onOpenQueryLibrary={handleOpenQueryLibrary}
|
||||
onOpenQueryInspector={handleOpenQueryInspector}
|
||||
isDebugMode={isDebugMode}
|
||||
debugPosition={debugPosition}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<QueryTransformDetailView
|
||||
selectedItem={selectedItem}
|
||||
panel={panel}
|
||||
tabs={tabs}
|
||||
onRemoveTransform={handleRemoveTransform}
|
||||
onToggleTransformVisibility={handleToggleTransformVisibility}
|
||||
isAddingTransform={transformPickerIndex != null}
|
||||
onAddTransformation={handleAddTransform}
|
||||
onCancelAddTransform={() => model.onTransformPicker(null)}
|
||||
transformationData={series}
|
||||
onGoToQueries={handleGoToQueries}
|
||||
queryLibraryMode={queryLibraryMode}
|
||||
onQueryLibrarySelect={handleQueryLibrarySelect}
|
||||
onQueryLibrarySave={handleQueryLibrarySave}
|
||||
onQueryLibraryClose={handleQueryLibraryClose}
|
||||
onOpenQueryLibrary={handleOpenQueryLibrary}
|
||||
onOpenQueryInspector={handleOpenQueryInspector}
|
||||
isDebugMode={isDebugMode}
|
||||
debugPosition={debugPosition}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -365,23 +349,3 @@ export function shouldShowAlertingTab(pluginId: string) {
|
||||
|
||||
return isGraph || isTimeseries;
|
||||
}
|
||||
|
||||
function getStyles(theme: GrafanaTheme2) {
|
||||
return {
|
||||
dataPane: css({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexGrow: 1,
|
||||
minHeight: 0,
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
paddingLeft: theme.spacing(2),
|
||||
}),
|
||||
unifiedLayout: css({
|
||||
flex: 1,
|
||||
minHeight: 0,
|
||||
background: theme.colors.background.primary,
|
||||
overflow: 'hidden',
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
import { Dispatch, SetStateAction, useCallback, useEffect, useMemo } from 'react';
|
||||
|
||||
import { DataTransformerConfig, GrafanaTheme2, SelectableValue } from '@grafana/data';
|
||||
import { t } from '@grafana/i18n';
|
||||
@@ -13,18 +13,35 @@ import { GRAFANA_RULES_SOURCE_NAME } from 'app/features/alerting/unified/utils/d
|
||||
import { ExpressionQueryType } from '../../../expressions/types';
|
||||
import { getQueryRunnerFor } from '../../utils/utils';
|
||||
|
||||
import { QueryLibraryMode } from './DetailView';
|
||||
import { PanelDataPane } from './PanelDataPane';
|
||||
import { PanelDataQueriesTab } from './PanelDataQueriesTab';
|
||||
import { PanelDataTransformationsTab } from './PanelDataTransformationsTab';
|
||||
import { QueryLibraryMode } from './QueryTransformDetailView';
|
||||
import { QueryTransformList } from './QueryTransformList';
|
||||
import { useQueryTransformItems } from './hooks';
|
||||
import { TabId } from './types';
|
||||
import { isDataTransformerConfig, queryItemId, transformItemId } from './utils';
|
||||
|
||||
export function PanelDataSidebarRendered({ model }: SceneComponentProps<PanelDataPane>) {
|
||||
const { panelRef, tabs, selectedQueryTransform, sidebarCollapsed, transformPickerIndex } = model.useState();
|
||||
const styles = useStyles2(getStyles, sidebarCollapsed);
|
||||
export enum SidebarSize {
|
||||
Mini = 'mini',
|
||||
Full = 'full',
|
||||
}
|
||||
|
||||
export interface SidebarState {
|
||||
size: SidebarSize;
|
||||
collapsed: boolean;
|
||||
}
|
||||
|
||||
export function PanelDataSidebar({
|
||||
model,
|
||||
sidebarState,
|
||||
setSidebarState,
|
||||
}: SceneComponentProps<PanelDataPane> & {
|
||||
sidebarState: SidebarState;
|
||||
setSidebarState: Dispatch<SetStateAction<SidebarState>>;
|
||||
}) {
|
||||
const { panelRef, tabs, selectedQueryTransform, transformPickerIndex } = model.useState();
|
||||
const styles = useStyles2(getStyles, sidebarState);
|
||||
|
||||
const panel = panelRef.resolve();
|
||||
|
||||
@@ -291,13 +308,13 @@ export function PanelDataSidebarRendered({ model }: SceneComponentProps<PanelDat
|
||||
// const sourceData = queryRunner?.useState();
|
||||
// const series = sourceData?.data?.series || [];
|
||||
|
||||
if (sidebarCollapsed) {
|
||||
if (sidebarState.collapsed) {
|
||||
return (
|
||||
<div className={styles.sidebarPane}>
|
||||
<Button
|
||||
icon="angle-right"
|
||||
variant="secondary"
|
||||
onClick={() => model.onCollapseSidebar(false)}
|
||||
onClick={() => setSidebarState((prevState) => ({ ...prevState, collapsed: true }))}
|
||||
tooltip={t('app.features.dashboardScene.panelEdit.panelDataPane.expandSidebar', 'Expand sidebar')}
|
||||
/>
|
||||
</div>
|
||||
@@ -311,7 +328,9 @@ export function PanelDataSidebarRendered({ model }: SceneComponentProps<PanelDat
|
||||
dataSourceItems={queryExpressionItems}
|
||||
transformItems={transformItems}
|
||||
selectedId={selectedId}
|
||||
onCollapseSidebar={() => model.onCollapseSidebar(true)}
|
||||
sidebarSize={sidebarState.size}
|
||||
onResizeSidebar={(size: SidebarSize) => setSidebarState((prevState) => ({ ...prevState, size }))}
|
||||
onCollapseSidebar={() => setSidebarState((prevState) => ({ ...prevState, collapsed: true }))}
|
||||
onSelect={(id) => {
|
||||
model.onChangeSelected(id);
|
||||
model.onTransformPicker(null);
|
||||
@@ -368,17 +387,24 @@ export function shouldShowAlertingTab(pluginId: string) {
|
||||
return isGraph || isTimeseries;
|
||||
}
|
||||
|
||||
function getStyles(theme: GrafanaTheme2, collapsed: boolean) {
|
||||
function getStyles(theme: GrafanaTheme2, sidebarState: SidebarState) {
|
||||
return {
|
||||
sidebarPane: css({
|
||||
overflow: 'hidden',
|
||||
height: '100%',
|
||||
width: collapsed ? 49 : 300,
|
||||
padding: collapsed ? theme.spacing(1) : 'unset',
|
||||
width: sidebarState.collapsed ? 49 : '100%',
|
||||
padding: sidebarState.collapsed ? theme.spacing(1) : 'unset',
|
||||
borderTopRightRadius: theme.shape.radius.md,
|
||||
borderTop: `1px solid ${theme.colors.border.weak}`,
|
||||
borderRight: `1px solid ${theme.colors.border.weak}`,
|
||||
border: `1px solid ${theme.colors.border.weak}`,
|
||||
borderBottom: 'none',
|
||||
background: theme.colors.background.primary,
|
||||
...(sidebarState.size === SidebarSize.Mini
|
||||
? {
|
||||
borderTopLeftRadius: theme.shape.radius.md,
|
||||
}
|
||||
: {
|
||||
borderLeft: 'none',
|
||||
}),
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
+209
@@ -0,0 +1,209 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { memo, useCallback, useRef } from 'react';
|
||||
|
||||
import { DataFrame, GrafanaTheme2, SelectableValue } from '@grafana/data';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { Trans } from '@grafana/i18n';
|
||||
import { SceneDataQuery, VizPanel } from '@grafana/scenes';
|
||||
import { Container, ScrollContainer, useStyles2 } from '@grafana/ui';
|
||||
import { isExpressionQuery } from 'app/features/expressions/guards';
|
||||
|
||||
import { ExpressionDetailView } from './ExpressionDetailView';
|
||||
import { PanelDataTransformationsTab, PanelDataTransformationsTabRendered } from './PanelDataTransformationsTab';
|
||||
import { QueryDetailView } from './QueryDetailView';
|
||||
import { QueryLibraryView, QueryLibraryViewRef } from './QueryLibraryView';
|
||||
import { QueryTransformDetailViewHeader } from './QueryTransformDetailViewHeader';
|
||||
import { TransformationPickerView } from './TransformationPickerView';
|
||||
import { TabId, QueryTransformItem } from './types';
|
||||
|
||||
export interface QueryLibraryMode {
|
||||
active: boolean;
|
||||
mode: 'browse' | 'save';
|
||||
currentQuery?: SceneDataQuery;
|
||||
}
|
||||
|
||||
interface QueryTransformDetailViewProps {
|
||||
selectedItem: QueryTransformItem | undefined;
|
||||
panel: VizPanel;
|
||||
tabs: Array<{ tabId: TabId }>;
|
||||
onRemoveTransform?: (index: number) => void;
|
||||
onToggleTransformVisibility?: (index: number) => void;
|
||||
isAddingTransform?: boolean;
|
||||
onAddTransformation?: (selectedItem: SelectableValue<string>, customOptions?: Record<string, unknown>) => void;
|
||||
onCancelAddTransform?: () => void;
|
||||
transformationData?: DataFrame[];
|
||||
onGoToQueries?: () => void;
|
||||
queryLibraryMode?: QueryLibraryMode;
|
||||
onQueryLibrarySelect?: (query: SceneDataQuery) => void;
|
||||
onQueryLibrarySave?: (name: string, description: string) => void;
|
||||
onQueryLibraryClose?: () => void;
|
||||
onOpenQueryLibrary?: (mode: 'browse' | 'save', index?: number) => void;
|
||||
onOpenQueryInspector?: () => void;
|
||||
isDebugMode?: boolean;
|
||||
debugPosition?: number;
|
||||
}
|
||||
|
||||
const QueryTransformDetailViewContent = ({
|
||||
selectedItem,
|
||||
panel,
|
||||
tabs,
|
||||
onRemoveTransform,
|
||||
onToggleTransformVisibility,
|
||||
isAddingTransform,
|
||||
onAddTransformation,
|
||||
onCancelAddTransform,
|
||||
transformationData,
|
||||
onGoToQueries,
|
||||
queryLibraryMode,
|
||||
onQueryLibrarySelect,
|
||||
onQueryLibrarySave,
|
||||
onQueryLibraryClose,
|
||||
onOpenQueryLibrary,
|
||||
onOpenQueryInspector,
|
||||
isDebugMode,
|
||||
debugPosition,
|
||||
styles,
|
||||
}: QueryTransformDetailViewProps & { styles: ReturnType<typeof getStyles> }) => {
|
||||
const queryLibraryRef = useRef<QueryLibraryViewRef>(null);
|
||||
|
||||
const handleSelectQueryFromHeader = useCallback(() => {
|
||||
queryLibraryRef.current?.selectCurrentQuery();
|
||||
}, []);
|
||||
|
||||
const handleSaveQueryFromHeader = useCallback(() => {
|
||||
queryLibraryRef.current?.saveQuery();
|
||||
}, []);
|
||||
|
||||
// Show transformation picker when in add mode
|
||||
if (isAddingTransform && onAddTransformation && onCancelAddTransform) {
|
||||
return (
|
||||
<TransformationPickerView
|
||||
data={transformationData || []}
|
||||
onAddTransformation={onAddTransformation}
|
||||
onCancel={onCancelAddTransform}
|
||||
onGoToQueries={onGoToQueries}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// Show QueryLibraryView when in query library mode
|
||||
if (queryLibraryMode?.active && onQueryLibraryClose) {
|
||||
return (
|
||||
<>
|
||||
<QueryTransformDetailViewHeader
|
||||
queryLibraryMode={queryLibraryMode.mode}
|
||||
onSelectQuery={queryLibraryMode.mode === 'browse' ? handleSelectQueryFromHeader : undefined}
|
||||
onSaveQuery={queryLibraryMode.mode === 'save' ? handleSaveQueryFromHeader : undefined}
|
||||
onClose={onQueryLibraryClose}
|
||||
/>
|
||||
<QueryLibraryView
|
||||
ref={queryLibraryRef}
|
||||
mode={queryLibraryMode.mode}
|
||||
currentQuery={queryLibraryMode.currentQuery}
|
||||
onSelectQuery={onQueryLibrarySelect}
|
||||
onSaveQuery={onQueryLibrarySave}
|
||||
onClose={onQueryLibraryClose}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (!selectedItem) {
|
||||
return (
|
||||
<div className={styles.emptyState}>
|
||||
<p>
|
||||
<Trans i18nKey="dashboard-scene.panel-data-pane.empty-state">Select a query or transformation to edit</Trans>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (selectedItem.type === 'query') {
|
||||
const query = selectedItem.data;
|
||||
return (
|
||||
<>
|
||||
<QueryTransformDetailViewHeader
|
||||
selectedItem={selectedItem}
|
||||
panel={panel}
|
||||
onOpenQueryLibrary={onOpenQueryLibrary}
|
||||
onOpenQueryInspector={onOpenQueryInspector}
|
||||
/>
|
||||
<ScrollContainer>
|
||||
<QueryDetailView panel={panel} query={query} queryIndex={selectedItem.index} />
|
||||
</ScrollContainer>
|
||||
</>
|
||||
);
|
||||
} else if (selectedItem.type === 'expression') {
|
||||
const data = selectedItem.data;
|
||||
if (isExpressionQuery(data)) {
|
||||
return (
|
||||
<>
|
||||
<QueryTransformDetailViewHeader
|
||||
selectedItem={selectedItem}
|
||||
panel={panel}
|
||||
onOpenQueryInspector={onOpenQueryInspector}
|
||||
/>
|
||||
<ScrollContainer>
|
||||
<ExpressionDetailView panel={panel} expression={data} expressionIndex={selectedItem.index} />
|
||||
</ScrollContainer>
|
||||
</>
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const transformsTab = tabs.find((t): t is PanelDataTransformationsTab => t.tabId === TabId.Transformations);
|
||||
if (transformsTab) {
|
||||
return (
|
||||
<>
|
||||
<QueryTransformDetailViewHeader
|
||||
selectedItem={selectedItem}
|
||||
panel={panel}
|
||||
onRemoveTransform={onRemoveTransform}
|
||||
onToggleTransformVisibility={onToggleTransformVisibility}
|
||||
isDebugMode={isDebugMode}
|
||||
debugPosition={debugPosition}
|
||||
/>
|
||||
<ScrollContainer>
|
||||
<Container>
|
||||
<PanelDataTransformationsTabRendered model={transformsTab} selectedIdx={selectedItem.index} />
|
||||
</Container>
|
||||
</ScrollContainer>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export const QueryTransformDetailView = memo((props: QueryTransformDetailViewProps) => {
|
||||
const styles = useStyles2(getStyles);
|
||||
return (
|
||||
<div className={styles.container} data-testid={selectors.components.PanelEditor.DataPane.content}>
|
||||
<QueryTransformDetailViewContent {...props} styles={styles} />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
QueryTransformDetailView.displayName = 'QueryTransformDetailView';
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => ({
|
||||
container: css({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
background: theme.colors.background.primary,
|
||||
border: `1px solid ${theme.colors.border.weak}`,
|
||||
borderTopLeftRadius: theme.shape.radius.md,
|
||||
borderTopRightRadius: theme.shape.radius.md,
|
||||
overflow: 'hidden',
|
||||
}),
|
||||
emptyState: css({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
height: '100%',
|
||||
color: theme.colors.text.secondary,
|
||||
fontSize: theme.typography.h5.fontSize,
|
||||
}),
|
||||
});
|
||||
+2
-2
@@ -67,7 +67,7 @@ interface QueryLibraryModeProps {
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
type DetailViewHeaderProps = ItemModeProps | QueryLibraryModeProps;
|
||||
type QueryTransformDetailViewHeaderProps = ItemModeProps | QueryLibraryModeProps;
|
||||
|
||||
const ITEM_CONFIG = (theme: GrafanaTheme2) => ({
|
||||
query: {
|
||||
@@ -682,7 +682,7 @@ function ItemHeader({
|
||||
}
|
||||
|
||||
// Main component that delegates to the appropriate sub-component
|
||||
export const DetailViewHeader = (props: DetailViewHeaderProps) => {
|
||||
export const QueryTransformDetailViewHeader = (props: QueryTransformDetailViewHeaderProps) => {
|
||||
if (props.queryLibraryMode) {
|
||||
return (
|
||||
<QueryLibraryHeader
|
||||
+10
-2
@@ -10,6 +10,7 @@ import { ExpressionQueryType } from 'app/features/expressions/types';
|
||||
import { AddDataItemMenu } from './AddDataItemMenu';
|
||||
import { AiModeCard } from './AiModeCard';
|
||||
import { ConnectionLines } from './ConnectionLines';
|
||||
import { SidebarSize } from './PanelDataSidebar';
|
||||
import { QueryTransformCard } from './QueryTransformCard';
|
||||
import { getItemHiddenState, restoreItemStates, syncItemsToDebugState } from './debugModeHelpers';
|
||||
import { usePanelDataPaneColors } from './theme';
|
||||
@@ -21,6 +22,7 @@ const CARD_HEIGHT = 70;
|
||||
interface QueryTransformListProps {
|
||||
allItems: QueryTransformItem[];
|
||||
dataSourceItems: QueryItem[];
|
||||
sidebarSize: SidebarSize;
|
||||
transformItems: TransformItem[];
|
||||
selectedId: string | null;
|
||||
onSelect: (id: string) => void;
|
||||
@@ -36,6 +38,7 @@ interface QueryTransformListProps {
|
||||
onReorderDataSources?: (startIndex: number, endIndex: number) => void;
|
||||
onReorderTransforms?: (startIndex: number, endIndex: number) => void;
|
||||
onAddOrganizeFieldsTransform?: () => void;
|
||||
onResizeSidebar: (size: SidebarSize) => void;
|
||||
onCollapseSidebar: () => void;
|
||||
onDebugStateChange?: (isDebugMode: boolean, debugPosition: number) => void;
|
||||
}
|
||||
@@ -46,6 +49,7 @@ export const QueryTransformList = memo(
|
||||
transformItems,
|
||||
allItems,
|
||||
selectedId,
|
||||
sidebarSize,
|
||||
onSelect,
|
||||
onAddQuery,
|
||||
onAddFromSavedQueries,
|
||||
@@ -59,6 +63,7 @@ export const QueryTransformList = memo(
|
||||
onReorderDataSources,
|
||||
onReorderTransforms,
|
||||
onAddOrganizeFieldsTransform,
|
||||
onResizeSidebar,
|
||||
onCollapseSidebar,
|
||||
onDebugStateChange,
|
||||
}: QueryTransformListProps) => {
|
||||
@@ -353,9 +358,12 @@ export const QueryTransformList = memo(
|
||||
<div className={styles.header}>
|
||||
<Stack justifyContent="space-between" alignItems="center" gap={2}>
|
||||
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}
|
||||
<div className={styles.headerTitle} onClick={onCollapseSidebar}>
|
||||
<div
|
||||
className={styles.headerTitle}
|
||||
onClick={() => onResizeSidebar(sidebarSize === SidebarSize.Mini ? SidebarSize.Full : SidebarSize.Mini)}
|
||||
>
|
||||
<Stack direction="row" alignItems="center" gap={1}>
|
||||
<Icon name="angle-down" />
|
||||
<Icon name={sidebarSize === SidebarSize.Mini ? 'expand-arrows' : 'compress-arrows'} />
|
||||
<span>{t('dashboard-scene.query-transform-list.header', 'Pipeline flow')}</span>
|
||||
</Stack>
|
||||
</div>
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { css, cx } from '@emotion/css';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useMeasure } from 'react-use';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { t } from '@grafana/i18n';
|
||||
import { SceneComponentProps, VizPanel } from '@grafana/scenes';
|
||||
import { Button, Spinner, ToolbarButton, useStyles2, useTheme2 } from '@grafana/ui';
|
||||
import { SceneComponentProps } from '@grafana/scenes';
|
||||
import { Spinner, ToolbarButton, useStyles2, useTheme2 } from '@grafana/ui';
|
||||
import { MIN_SUGGESTIONS_PANE_WIDTH } from 'app/features/panel/suggestions/constants';
|
||||
|
||||
import { useEditPaneCollapsed } from '../edit-pane/shared';
|
||||
@@ -13,7 +14,7 @@ import { NavToolbarActions } from '../scene/NavToolbarActions';
|
||||
import { UnlinkModal } from '../scene/UnlinkModal';
|
||||
import { getDashboardSceneFor, getLibraryPanelBehavior } from '../utils/utils';
|
||||
|
||||
import { PanelDataSidebarRendered } from './PanelDataPane/PanelDataSidebar';
|
||||
import { PanelDataSidebar, SidebarSize, SidebarState } from './PanelDataPane/PanelDataSidebar';
|
||||
import { PanelEditor } from './PanelEditor';
|
||||
import { SaveLibraryVizPanelModal } from './SaveLibraryVizPanelModal';
|
||||
import { useSnappingSplitter } from './splitter/useSnappingSplitter';
|
||||
@@ -22,8 +23,13 @@ import { scrollReflowMediaCondition, useScrollReflowLimit } from './useScrollRef
|
||||
export function PanelEditorRenderer({ model }: SceneComponentProps<PanelEditor>) {
|
||||
const dashboard = getDashboardSceneFor(model);
|
||||
const { optionsPane } = model.useState();
|
||||
const styles = useStyles2(getStyles);
|
||||
const styles = useStyles2(getWrapperStyles);
|
||||
const [isInitiallyCollapsed, setIsCollapsed] = useEditPaneCollapsed();
|
||||
const [containerRef, { height: containerHeight }] = useMeasure<HTMLDivElement>();
|
||||
|
||||
useEffect(() => {
|
||||
console.log('PanelEditorRenderer containerHeight', containerHeight);
|
||||
}, [containerHeight]);
|
||||
|
||||
const isScrollingLayout = useScrollReflowLimit();
|
||||
|
||||
@@ -45,7 +51,7 @@ export function PanelEditorRenderer({ model }: SceneComponentProps<PanelEditor>)
|
||||
}, [splitterState.collapsed, setIsCollapsed]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div style={{ height: '100%' }} ref={containerRef}>
|
||||
<NavToolbarActions dashboard={dashboard} />
|
||||
<div
|
||||
{...containerProps}
|
||||
@@ -53,7 +59,7 @@ export function PanelEditorRenderer({ model }: SceneComponentProps<PanelEditor>)
|
||||
data-testid={selectors.components.PanelEditor.General.content}
|
||||
>
|
||||
<div {...primaryProps} className={cx(primaryProps.className, styles.body)}>
|
||||
<VizAndDataPane model={model} />
|
||||
<VizAndDataPane model={model} containerHeight={Math.max(containerHeight, 500)} />
|
||||
</div>
|
||||
<div {...splitterProps} />
|
||||
<div {...secondaryProps} className={cx(secondaryProps.className, styles.optionsPane)}>
|
||||
@@ -76,160 +82,114 @@ export function PanelEditorRenderer({ model }: SceneComponentProps<PanelEditor>)
|
||||
{!splitterState.collapsed && !optionsPane && <Spinner />}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function VizAndDataPane({ model }: SceneComponentProps<PanelEditor>) {
|
||||
function VizAndDataPane({
|
||||
model,
|
||||
containerHeight = 800,
|
||||
}: SceneComponentProps<PanelEditor> & { containerHeight?: number }) {
|
||||
const dashboard = getDashboardSceneFor(model);
|
||||
const { dataPane, showLibraryPanelSaveModal, showLibraryPanelUnlinkModal, tableView } = model.useState();
|
||||
const panel = model.getPanel();
|
||||
const libraryPanel = getLibraryPanelBehavior(panel);
|
||||
const { controls } = dashboard.useState();
|
||||
const styles = useStyles2(getStyles);
|
||||
const [sidebarState, setSidebarState] = useState<SidebarState>({ size: SidebarSize.Mini, collapsed: false });
|
||||
const [vizRef, { height: vizHeight }] = useMeasure<HTMLDivElement>();
|
||||
|
||||
const styles = useStyles2(getStyles, sidebarState);
|
||||
|
||||
const isScrollingLayout = useScrollReflowLimit();
|
||||
|
||||
const { containerProps, primaryProps, secondaryProps, splitterProps, splitterState, onToggleCollapse } =
|
||||
useSnappingSplitter({
|
||||
direction: 'column',
|
||||
dragPosition: 'start',
|
||||
initialSize: 0.5,
|
||||
collapseBelowPixels: 150,
|
||||
disabled: isScrollingLayout,
|
||||
});
|
||||
const gridStyles = useMemo(() => {
|
||||
const rows = [];
|
||||
const grid = [];
|
||||
|
||||
containerProps.className = cx(containerProps.className, styles.container);
|
||||
if (controls) {
|
||||
rows.push('32px');
|
||||
grid.push(['controls', 'controls']);
|
||||
}
|
||||
|
||||
if (!dataPane && !isScrollingLayout) {
|
||||
primaryProps.style.flexGrow = 1;
|
||||
grid.push(['viz', 'viz']);
|
||||
rows.push(`${vizHeight}px`);
|
||||
|
||||
if (dataPane) {
|
||||
rows.push('auto');
|
||||
grid.push(['sidebar', 'data-pane']);
|
||||
if (sidebarState.size === SidebarSize.Full) {
|
||||
for (let i = 0; i < grid.length; i++) {
|
||||
grid[i][0] = 'sidebar';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
height: containerHeight,
|
||||
maxHeight: containerHeight,
|
||||
gridTemplateAreas: '\n' + grid.map((row) => `"${row.join(' ')}"`).join('\n'),
|
||||
gridTemplateRows: rows.map((r) => r).join(' '),
|
||||
};
|
||||
}, [controls, dataPane, sidebarState.size, vizHeight, containerHeight]);
|
||||
|
||||
if (!containerHeight) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cx(styles.pageContainer, controls && styles.pageContainerWithControls)}>
|
||||
<div className={styles.pageContainer} style={gridStyles}>
|
||||
{controls && (
|
||||
<div className={styles.controlsWrapper}>
|
||||
<controls.Component model={controls} />
|
||||
</div>
|
||||
)}
|
||||
{dataPane && (
|
||||
<div className={styles.sidebar}>
|
||||
<PanelDataSidebarRendered model={dataPane} />
|
||||
</div>
|
||||
)}
|
||||
<div {...containerProps}>
|
||||
<div {...primaryProps} className={cx(primaryProps.className, isScrollingLayout && styles.fixedSizeViz)}>
|
||||
<VizWrapper panel={panel} tableView={tableView} />
|
||||
</div>
|
||||
{showLibraryPanelSaveModal && libraryPanel && (
|
||||
<SaveLibraryVizPanelModal
|
||||
libraryPanel={libraryPanel}
|
||||
onDismiss={model.onDismissLibraryPanelSaveModal}
|
||||
onConfirm={model.onConfirmSaveLibraryPanel}
|
||||
onDiscard={model.onDiscard}
|
||||
></SaveLibraryVizPanelModal>
|
||||
)}
|
||||
{showLibraryPanelUnlinkModal && libraryPanel && (
|
||||
<UnlinkModal
|
||||
onDismiss={model.onDismissUnlinkLibraryPanelModal}
|
||||
onConfirm={model.onConfirmUnlinkLibraryPanel}
|
||||
isOpen
|
||||
/>
|
||||
)}
|
||||
{dataPane && (
|
||||
<>
|
||||
<div {...splitterProps} />
|
||||
<div
|
||||
{...secondaryProps}
|
||||
className={cx(secondaryProps.className, isScrollingLayout && styles.fullSizeEditor)}
|
||||
>
|
||||
{splitterState.collapsed && (
|
||||
<div className={styles.expandDataPane}>
|
||||
<Button
|
||||
tooltip={t('dashboard-scene.viz-and-data-pane.tooltip-open-query-pane', 'Open query pane')}
|
||||
icon={'arrow-to-right'}
|
||||
onClick={onToggleCollapse}
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
className={styles.openDataPaneButton}
|
||||
aria-label={t('dashboard-scene.viz-and-data-pane.aria-label-open-query-pane', 'Open query pane')}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{!splitterState.collapsed && <dataPane.Component model={dataPane} />}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div
|
||||
className={cx(styles.viz, isScrollingLayout && styles.fixedSizeViz)}
|
||||
ref={vizRef}
|
||||
style={{ height: containerHeight / 2, maxHeight: containerHeight - 80 }}
|
||||
>
|
||||
{tableView ? <tableView.Component model={tableView} /> : <panel.Component model={panel} />}
|
||||
</div>
|
||||
|
||||
{dataPane && (
|
||||
<>
|
||||
<div className={cx(styles.dataPane, isScrollingLayout && styles.fullSizeEditor)}>
|
||||
<dataPane.Component model={dataPane} />
|
||||
</div>
|
||||
<div className={styles.sidebar}>
|
||||
<PanelDataSidebar model={dataPane} sidebarState={sidebarState} setSidebarState={setSidebarState} />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{showLibraryPanelSaveModal && libraryPanel && (
|
||||
<SaveLibraryVizPanelModal
|
||||
libraryPanel={libraryPanel}
|
||||
onDismiss={model.onDismissLibraryPanelSaveModal}
|
||||
onConfirm={model.onConfirmSaveLibraryPanel}
|
||||
onDiscard={model.onDiscard}
|
||||
></SaveLibraryVizPanelModal>
|
||||
)}
|
||||
{showLibraryPanelUnlinkModal && libraryPanel && (
|
||||
<UnlinkModal
|
||||
onDismiss={model.onDismissUnlinkLibraryPanelModal}
|
||||
onConfirm={model.onConfirmUnlinkLibraryPanel}
|
||||
isOpen
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface VizWrapperProps {
|
||||
panel: VizPanel;
|
||||
tableView?: VizPanel;
|
||||
}
|
||||
|
||||
function VizWrapper({ panel, tableView }: VizWrapperProps) {
|
||||
const styles = useStyles2(getStyles);
|
||||
const panelToShow = tableView ?? panel;
|
||||
|
||||
return (
|
||||
<div className={styles.vizWrapper}>
|
||||
<panelToShow.Component model={panelToShow} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function getStyles(theme: GrafanaTheme2) {
|
||||
function getWrapperStyles(theme: GrafanaTheme2) {
|
||||
const scrollReflowMediaQuery = '@media ' + scrollReflowMediaCondition;
|
||||
return {
|
||||
pageContainer: css({
|
||||
display: 'grid',
|
||||
gridTemplateAreas: `
|
||||
"sidebar panels"`,
|
||||
gridTemplateColumns: `auto 1fr`,
|
||||
gridTemplateRows: '1fr',
|
||||
height: '100%',
|
||||
[scrollReflowMediaQuery]: {
|
||||
gridTemplateColumns: `100%`,
|
||||
},
|
||||
}),
|
||||
pageContainerWithControls: css({
|
||||
gridTemplateAreas: `
|
||||
"sidebar controls"
|
||||
"sidebar panels"`,
|
||||
gridTemplateRows: 'auto 1fr',
|
||||
}),
|
||||
container: css({
|
||||
gridArea: 'panels',
|
||||
height: '100%',
|
||||
}),
|
||||
sidebar: css({
|
||||
gridArea: 'sidebar',
|
||||
height: '100%',
|
||||
maxHeight: '100%',
|
||||
overflow: 'auto',
|
||||
paddingTop: theme.spacing(2),
|
||||
}),
|
||||
expandDataSidebar: css({
|
||||
width: theme.spacing(6),
|
||||
padding: theme.spacing(1),
|
||||
}),
|
||||
canvasContent: css({
|
||||
label: 'canvas-content',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexBasis: '100%',
|
||||
flexGrow: 1,
|
||||
minHeight: 0,
|
||||
width: '100%',
|
||||
}),
|
||||
content: css({
|
||||
position: 'absolute',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
overflow: 'unset',
|
||||
paddingTop: theme.spacing(2),
|
||||
[scrollReflowMediaQuery]: {
|
||||
height: 'auto',
|
||||
display: 'grid',
|
||||
@@ -251,7 +211,6 @@ function getStyles(theme: GrafanaTheme2) {
|
||||
flexDirection: 'column',
|
||||
borderLeft: `1px solid ${theme.colors.border.weak}`,
|
||||
background: theme.colors.background.primary,
|
||||
marginTop: theme.spacing(2),
|
||||
borderTop: `1px solid ${theme.colors.border.weak}`,
|
||||
borderTopLeftRadius: theme.shape.radius.default,
|
||||
}),
|
||||
@@ -260,24 +219,55 @@ function getStyles(theme: GrafanaTheme2) {
|
||||
flexDirection: 'column',
|
||||
padding: theme.spacing(2, 1),
|
||||
}),
|
||||
expandDataPane: css({
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
padding: theme.spacing(1),
|
||||
borderTop: `1px solid ${theme.colors.border.weak}`,
|
||||
borderRight: `1px solid ${theme.colors.border.weak}`,
|
||||
background: theme.colors.background.primary,
|
||||
flexGrow: 1,
|
||||
justifyContent: 'space-around',
|
||||
}),
|
||||
rotate180: css({
|
||||
rotate: '180deg',
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
function getStyles(theme: GrafanaTheme2, sidebarState: SidebarState) {
|
||||
const scrollReflowMediaQuery = '@media ' + scrollReflowMediaCondition;
|
||||
return {
|
||||
pageContainer: css({
|
||||
display: 'grid',
|
||||
gap: theme.spacing(2),
|
||||
gridTemplateColumns: `auto 1fr`,
|
||||
overflow: 'hidden',
|
||||
[scrollReflowMediaQuery]: {
|
||||
gridTemplateColumns: `100%`,
|
||||
},
|
||||
}),
|
||||
sidebar: css({
|
||||
gridArea: 'sidebar',
|
||||
overflow: 'auto',
|
||||
resize: 'horizontal',
|
||||
minWidth: 285,
|
||||
maxWidth: 400,
|
||||
...(sidebarState.size === SidebarSize.Mini && {
|
||||
paddingLeft: theme.spacing(2),
|
||||
}),
|
||||
}),
|
||||
viz: css({
|
||||
gridArea: 'viz',
|
||||
overflow: 'auto',
|
||||
resize: 'vertical',
|
||||
height: '100%',
|
||||
minHeight: 100,
|
||||
...(sidebarState.size === SidebarSize.Mini && {
|
||||
paddingLeft: theme.spacing(2),
|
||||
}),
|
||||
}),
|
||||
dataPane: css({
|
||||
gridArea: 'data-pane',
|
||||
overflow: 'hidden',
|
||||
}),
|
||||
controlsWrapper: css({
|
||||
gridArea: 'controls',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexGrow: 0,
|
||||
gridArea: 'controls',
|
||||
...(sidebarState.size === SidebarSize.Mini && {
|
||||
paddingLeft: theme.spacing(2),
|
||||
}),
|
||||
}),
|
||||
openDataPaneButton: css({
|
||||
width: theme.spacing(8),
|
||||
@@ -286,11 +276,6 @@ function getStyles(theme: GrafanaTheme2) {
|
||||
rotate: '-90deg',
|
||||
},
|
||||
}),
|
||||
vizWrapper: css({
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
paddingLeft: theme.spacing(2),
|
||||
}),
|
||||
fixedSizeViz: css({
|
||||
height: '100vh',
|
||||
}),
|
||||
|
||||
@@ -250,12 +250,12 @@ function getStyles(theme: GrafanaTheme2) {
|
||||
return {
|
||||
controls: css({
|
||||
gap: theme.spacing(1),
|
||||
padding: theme.spacing(2, 2, 1, 2),
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'nowrap',
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
marginLeft: 'auto',
|
||||
marginBottom: theme.spacing(-1),
|
||||
[theme.breakpoints.down('sm')]: {
|
||||
flexDirection: 'column-reverse',
|
||||
alignItems: 'stretch',
|
||||
|
||||
Reference in New Issue
Block a user