Scopes: move scope dashboard list toggle to canvas/page (#115131)
* Scopes: move scope dashboard list toggle to canvas/page * Updates * Updates * Fix test * Update
This commit is contained in:
@@ -19,6 +19,7 @@ import {
|
|||||||
} from '@grafana/scenes';
|
} from '@grafana/scenes';
|
||||||
import { Box, Button, useStyles2 } from '@grafana/ui';
|
import { Box, Button, useStyles2 } from '@grafana/ui';
|
||||||
import { playlistSrv } from 'app/features/playlist/PlaylistSrv';
|
import { playlistSrv } from 'app/features/playlist/PlaylistSrv';
|
||||||
|
import { ContextualNavigationPaneToggle } from 'app/features/scopes/dashboards/ContextualNavigationPaneToggle';
|
||||||
|
|
||||||
import { PanelEditControls } from '../panel-edit/PanelEditControls';
|
import { PanelEditControls } from '../panel-edit/PanelEditControls';
|
||||||
import { getDashboardSceneFor } from '../utils/utils';
|
import { getDashboardSceneFor } from '../utils/utils';
|
||||||
@@ -172,6 +173,9 @@ function DashboardControlsRenderer({ model }: SceneComponentProps<DashboardContr
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
{config.featureToggles.scopeFilters && !editPanel && (
|
||||||
|
<ContextualNavigationPaneToggle className={styles.contextualNavToggle} hideWhenOpen={true} />
|
||||||
|
)}
|
||||||
{!hideVariableControls && (
|
{!hideVariableControls && (
|
||||||
<>
|
<>
|
||||||
<VariableControls dashboard={dashboard} />
|
<VariableControls dashboard={dashboard} />
|
||||||
@@ -287,5 +291,9 @@ function getStyles(theme: GrafanaTheme2) {
|
|||||||
flexWrap: 'wrap',
|
flexWrap: 'wrap',
|
||||||
marginLeft: 'auto',
|
marginLeft: 'auto',
|
||||||
}),
|
}),
|
||||||
|
contextualNavToggle: css({
|
||||||
|
display: 'inline-flex',
|
||||||
|
margin: theme.spacing(0, 1, 1, 0),
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { t } from '@grafana/i18n';
|
||||||
|
import { useScopes } from '@grafana/runtime';
|
||||||
|
import { ToolbarButton } from '@grafana/ui';
|
||||||
|
|
||||||
|
import { useScopesServices } from '../ScopesContextProvider';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
className?: string;
|
||||||
|
hideWhenOpen?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ContextualNavigationPaneToggle({ className, hideWhenOpen }: Props) {
|
||||||
|
const scopes = useScopes();
|
||||||
|
const services = useScopesServices();
|
||||||
|
|
||||||
|
if (!scopes || !services) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { scopesDashboardsService } = services;
|
||||||
|
const { readOnly, drawerOpened } = scopes.state;
|
||||||
|
|
||||||
|
if (hideWhenOpen && drawerOpened) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dashboardsIconLabel = readOnly
|
||||||
|
? t('scopes.dashboards.toggle.disabled', 'Suggested dashboards list is disabled due to read only mode')
|
||||||
|
: drawerOpened
|
||||||
|
? t('scopes.dashboards.toggle.collapse', 'Collapse suggested dashboards list')
|
||||||
|
: t('scopes.dashboards.toggle.expand', 'Expand suggested dashboards list');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={className}>
|
||||||
|
<ToolbarButton
|
||||||
|
icon="web-section-alt"
|
||||||
|
aria-label={dashboardsIconLabel}
|
||||||
|
tooltip={dashboardsIconLabel}
|
||||||
|
data-testid="scopes-dashboards-expand"
|
||||||
|
disabled={readOnly}
|
||||||
|
onClick={scopesDashboardsService.toggleDrawer}
|
||||||
|
variant={'canvas'}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { css, cx } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import { useObservable } from 'react-use';
|
import { useObservable } from 'react-use';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
@@ -34,22 +34,22 @@ export function ScopesDashboards() {
|
|||||||
if (!loading) {
|
if (!loading) {
|
||||||
if (forScopeNames.length === 0) {
|
if (forScopeNames.length === 0) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div className={styles.container} data-testid="scopes-dashboards-container">
|
||||||
className={cx(styles.container, styles.noResultsContainer)}
|
<ScopesDashboardsTreeSearch disabled={loading} query={searchQuery} onChange={changeSearchQuery} />
|
||||||
data-testid="scopes-dashboards-notFoundNoScopes"
|
|
||||||
>
|
<div className={styles.noResultsContainer} data-testid="scopes-dashboards-notFoundNoScopes">
|
||||||
<Trans i18nKey="scopes.dashboards.noResultsNoScopes">No scopes selected</Trans>
|
<Trans i18nKey="scopes.dashboards.noResultsNoScopes">No scopes selected</Trans>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else if (dashboards.length === 0 && scopeNavigations.length === 0) {
|
} else if (dashboards.length === 0 && scopeNavigations.length === 0) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div className={styles.container} data-testid="scopes-dashboards-container">
|
||||||
className={cx(styles.container, styles.noResultsContainer)}
|
<div className={styles.noResultsContainer} data-testid="scopes-dashboards-notFoundForScope">
|
||||||
data-testid="scopes-dashboards-notFoundForScope"
|
<Trans i18nKey="scopes.dashboards.noResultsForScopes">
|
||||||
>
|
No dashboards or links found for the selected scopes
|
||||||
<Trans i18nKey="scopes.dashboards.noResultsForScopes">
|
</Trans>
|
||||||
No dashboards or links found for the selected scopes
|
</div>
|
||||||
</Trans>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -94,13 +94,14 @@ export function ScopesDashboards() {
|
|||||||
const getStyles = (theme: GrafanaTheme2) => {
|
const getStyles = (theme: GrafanaTheme2) => {
|
||||||
return {
|
return {
|
||||||
container: css({
|
container: css({
|
||||||
backgroundColor: theme.colors.background.primary,
|
backgroundColor: theme.colors.background.canvas,
|
||||||
borderRight: `1px solid ${theme.colors.border.weak}`,
|
borderRight: `1px solid ${theme.colors.border.weak}`,
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
gap: theme.spacing(1),
|
gap: theme.spacing(1),
|
||||||
padding: theme.spacing(2),
|
padding: theme.spacing(0, 2),
|
||||||
|
margin: theme.spacing(2, 0),
|
||||||
width: theme.spacing(37.5),
|
width: theme.spacing(37.5),
|
||||||
}),
|
}),
|
||||||
noResultsContainer: css({
|
noResultsContainer: css({
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import { GrafanaTheme2 } from '@grafana/data';
|
|||||||
import { t } from '@grafana/i18n';
|
import { t } from '@grafana/i18n';
|
||||||
import { FilterInput, useStyles2 } from '@grafana/ui';
|
import { FilterInput, useStyles2 } from '@grafana/ui';
|
||||||
|
|
||||||
|
import { ContextualNavigationPaneToggle } from './ContextualNavigationPaneToggle';
|
||||||
|
|
||||||
export interface ScopesDashboardsTreeSearchProps {
|
export interface ScopesDashboardsTreeSearchProps {
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
query: string;
|
query: string;
|
||||||
@@ -42,6 +44,7 @@ export function ScopesDashboardsTreeSearch({ disabled, query, onChange }: Scopes
|
|||||||
data-testid="scopes-dashboards-search"
|
data-testid="scopes-dashboards-search"
|
||||||
onChange={(value) => setInputState({ value, dirty: true })}
|
onChange={(value) => setInputState({ value, dirty: true })}
|
||||||
/>
|
/>
|
||||||
|
<ContextualNavigationPaneToggle />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -49,6 +52,8 @@ export function ScopesDashboardsTreeSearch({ disabled, query, onChange }: Scopes
|
|||||||
const getStyles = (theme: GrafanaTheme2) => {
|
const getStyles = (theme: GrafanaTheme2) => {
|
||||||
return {
|
return {
|
||||||
container: css({
|
container: css({
|
||||||
|
display: 'flex',
|
||||||
|
gap: theme.spacing(1),
|
||||||
flex: '0 1 auto',
|
flex: '0 1 auto',
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { Observable } from 'rxjs';
|
|||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
import { Trans, t } from '@grafana/i18n';
|
import { Trans, t } from '@grafana/i18n';
|
||||||
import { useScopes } from '@grafana/runtime';
|
import { useScopes } from '@grafana/runtime';
|
||||||
import { Button, Drawer, ErrorBoundary, ErrorWithStack, IconButton, Spinner, Text, useStyles2 } from '@grafana/ui';
|
import { Button, Drawer, ErrorBoundary, ErrorWithStack, Spinner, Text, useStyles2 } from '@grafana/ui';
|
||||||
import { getModKey } from 'app/core/utils/browser';
|
import { getModKey } from 'app/core/utils/browser';
|
||||||
|
|
||||||
import { useScopesServices } from '../ScopesContextProvider';
|
import { useScopesServices } from '../ScopesContextProvider';
|
||||||
@@ -54,8 +54,8 @@ export const ScopesSelector = () => {
|
|||||||
tree,
|
tree,
|
||||||
scopes: scopesMap,
|
scopes: scopesMap,
|
||||||
} = selectorServiceState;
|
} = selectorServiceState;
|
||||||
const { scopesService, scopesSelectorService, scopesDashboardsService } = services;
|
const { scopesService, scopesSelectorService } = services;
|
||||||
const { readOnly, drawerOpened, loading } = scopes.state;
|
const { readOnly, loading } = scopes.state;
|
||||||
const {
|
const {
|
||||||
open,
|
open,
|
||||||
removeAllScopes,
|
removeAllScopes,
|
||||||
@@ -70,24 +70,8 @@ export const ScopesSelector = () => {
|
|||||||
|
|
||||||
const recentScopes = getRecentScopes();
|
const recentScopes = getRecentScopes();
|
||||||
|
|
||||||
const dashboardsIconLabel = readOnly
|
|
||||||
? t('scopes.dashboards.toggle.disabled', 'Suggested dashboards list is disabled due to read only mode')
|
|
||||||
: drawerOpened
|
|
||||||
? t('scopes.dashboards.toggle.collapse', 'Collapse suggested dashboards list')
|
|
||||||
: t('scopes.dashboards.toggle.expand', 'Expand suggested dashboards list');
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<IconButton
|
|
||||||
name="web-section-alt"
|
|
||||||
className={styles.dashboards}
|
|
||||||
aria-label={dashboardsIconLabel}
|
|
||||||
tooltip={dashboardsIconLabel}
|
|
||||||
data-testid="scopes-dashboards-expand"
|
|
||||||
disabled={readOnly}
|
|
||||||
onClick={scopesDashboardsService.toggleDrawer}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<ScopesInput
|
<ScopesInput
|
||||||
nodes={nodes}
|
nodes={nodes}
|
||||||
scopes={scopesMap}
|
scopes={scopesMap}
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ import {
|
|||||||
expectNoDashboardsForFilter,
|
expectNoDashboardsForFilter,
|
||||||
expectNoDashboardsForScope,
|
expectNoDashboardsForScope,
|
||||||
expectNoDashboardsNoScopes,
|
expectNoDashboardsNoScopes,
|
||||||
expectNoDashboardsSearch,
|
|
||||||
} from './utils/assertions';
|
} from './utils/assertions';
|
||||||
import {
|
import {
|
||||||
alternativeDashboardWithRootFolder,
|
alternativeDashboardWithRootFolder,
|
||||||
@@ -304,14 +303,12 @@ describe('Dashboards list', () => {
|
|||||||
it('Shows a proper message when no scopes are selected', async () => {
|
it('Shows a proper message when no scopes are selected', async () => {
|
||||||
await toggleDashboards();
|
await toggleDashboards();
|
||||||
expectNoDashboardsNoScopes();
|
expectNoDashboardsNoScopes();
|
||||||
expectNoDashboardsSearch();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Does not show the input when there are no dashboards found for scope', async () => {
|
it('Does not show the input when there are no dashboards found for scope', async () => {
|
||||||
await updateScopes(scopesService, ['cloud']);
|
await updateScopes(scopesService, ['cloud']);
|
||||||
await toggleDashboards();
|
await toggleDashboards();
|
||||||
expectNoDashboardsForScope();
|
expectNoDashboardsForScope();
|
||||||
expectNoDashboardsSearch();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Shows the input and a message when there are no dashboards found for filter', async () => {
|
it('Shows the input and a message when there are no dashboards found for filter', async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user