- {input}
+
+
);
}
@@ -131,13 +119,16 @@ export interface ScopesTooltipProps {
nodes: NodesMap;
scopes: ScopesMap;
appliedScopes: SelectedScope[];
+ disabled?: boolean;
+ onRemoveAllClick?: () => void;
}
-function ScopesTooltip({ nodes, scopes, appliedScopes }: ScopesTooltipProps) {
- const styles = useStyles2(getStyles);
+function ScopesTooltip({ nodes, scopes, appliedScopes, onRemoveAllClick, disabled }: ScopesTooltipProps) {
+ if (appliedScopes.length === 0) {
+ return t('scopes.selector.input.tooltip', 'Select scope');
+ }
const nicePath = getScopesPath(appliedScopes, nodes);
-
const scopeNames = appliedScopes.map((s) => {
if (s.scopeNodeId) {
return nodes[s.scopeNodeId]?.spec.title || s.scopeNodeId;
@@ -146,21 +137,76 @@ function ScopesTooltip({ nodes, scopes, appliedScopes }: ScopesTooltipProps) {
}
});
+ const parentPaths = nicePath && nicePath.length > 0 ? nicePath.join(' > ') + ' > ' : '';
+
return (
- <>
-
- {(nicePath && nicePath.length > 0 ? nicePath.join(' > ') + ' > ' : '') + scopeNames.join(', ')}
-
- >
+
+ {parentPaths + scopeNames.join(', ')}
+ {!disabled && (
+
+ Remove all
+
+ )}
+
);
}
const getStyles = (theme: GrafanaTheme2) => {
+ const baseStyles = getInputStyles({ theme });
+
return {
- scopePath: css({
- color: theme.colors.text.primary,
- fontSize: theme.typography.pxToRem(12),
- margin: theme.spacing(0, 0),
+ prefix: baseStyles.prefix,
+ suffix: baseStyles.suffix,
+ fakeInput: css([
+ baseStyles.input,
+ {
+ width: 'auto',
+ minWidth: 60,
+ height: theme.spacing(theme.components.height.md),
+ maxWidth: '40%',
+ position: 'relative',
+ display: 'flex',
+ alignItems: 'center',
+ textAlign: 'left',
+ overflow: 'hidden',
+ whiteSpace: 'nowrap',
+ paddingRight: 28,
+ flexGrow: 0,
+
+ '&:disabled': cx(
+ baseStyles.inputDisabled,
+ css({
+ cursor: 'not-allowed',
+ })
+ ),
+
+ // We want the focus styles to appear only when tabbing through, not when clicking the button
+ // (and when focus is restored after command palette closes)
+ '&:focus': {
+ outline: 'unset',
+ boxShadow: 'unset',
+ },
+
+ '&:focus-visible': getFocusStyles(theme),
+ },
+ ]),
+ text: css({
+ textOverflow: 'ellipsis',
+ overflow: 'hidden',
+ }),
+ parentNode: css({
+ marginRight: theme.spacing(1),
+ paddingRight: theme.spacing(1),
+ borderRight: `1px solid ${theme.colors.border.weak}`,
+ color: theme.colors.text.secondary,
}),
};
};
diff --git a/public/app/features/scopes/selector/ScopesSelector.tsx b/public/app/features/scopes/selector/ScopesSelector.tsx
index 061829d77b7..cdb28acddf8 100644
--- a/public/app/features/scopes/selector/ScopesSelector.tsx
+++ b/public/app/features/scopes/selector/ScopesSelector.tsx
@@ -7,7 +7,6 @@ import { GrafanaTheme2 } from '@grafana/data';
import { Trans, t } from '@grafana/i18n';
import { useScopes } from '@grafana/runtime';
import { Button, Drawer, ErrorBoundary, ErrorWithStack, IconButton, Spinner, Text, useStyles2 } from '@grafana/ui';
-import { useGrafana } from 'app/core/context/GrafanaContext';
import { getModKey } from 'app/core/utils/browser';
import { useScopesServices } from '../ScopesContextProvider';
@@ -17,10 +16,7 @@ import { ScopesSelectorServiceState } from './ScopesSelectorService';
import { ScopesTree } from './ScopesTree';
export const ScopesSelector = () => {
- const { chrome } = useGrafana();
- const chromeState = chrome.useState();
- const menuDockedAndOpen = !chromeState.chromeless && chromeState.megaMenuDocked && chromeState.megaMenuOpen;
- const styles = useStyles2(getStyles, menuDockedAndOpen);
+ const styles = useStyles2(getStyles);
const scopes = useScopes();
const services = useScopesServices();
@@ -81,7 +77,7 @@ export const ScopesSelector = () => {
: t('scopes.dashboards.toggle.expand', 'Expand suggested dashboards list');
return (
-
+ <>
{
)}
-
+ >
);
};
-const getStyles = (theme: GrafanaTheme2, menuDockedAndOpen: boolean) => {
+const getStyles = (theme: GrafanaTheme2) => {
return {
- container: css({
- display: 'flex',
- flexDirection: 'row',
- paddingLeft: menuDockedAndOpen ? theme.spacing(2) : 'unset',
- }),
dashboards: css({
color: theme.colors.text.secondary,
- marginRight: theme.spacing(2),
'&:hover': css({
color: theme.colors.text.primary,
diff --git a/public/app/features/scopes/tests/selector.test.ts b/public/app/features/scopes/tests/selector.test.ts
index 4c8f3b3deb0..b7083a7fc13 100644
--- a/public/app/features/scopes/tests/selector.test.ts
+++ b/public/app/features/scopes/tests/selector.test.ts
@@ -15,6 +15,7 @@ import {
expandResultApplications,
selectRecentScope,
clearSelector,
+ hoverSelector,
} from './utils/actions';
import {
expectRecentScope,
@@ -121,7 +122,8 @@ describe('Selector', () => {
await selectResultApplicationsMimir();
await applyScopes();
- // recent scopes only show on top level, so we need to make sure the scopes tree is not exapnded.
+ // recent scopes only show on top level, so we need to make sure the scopes tree is not expanded.
+ await hoverSelector();
await clearSelector();
await openSelector();
diff --git a/public/app/features/scopes/tests/utils/actions.ts b/public/app/features/scopes/tests/utils/actions.ts
index b018ab674be..fc5fde93193 100644
--- a/public/app/features/scopes/tests/utils/actions.ts
+++ b/public/app/features/scopes/tests/utils/actions.ts
@@ -47,6 +47,7 @@ const type = async (selector: () => HTMLInputElement, value: string) => {
export const updateScopes = async (service: ScopesService, scopes: string[]) =>
act(async () => service.changeScopes(scopes));
export const openSelector = async () => click(getSelectorInput);
+export const hoverSelector = async () => fireEvent.mouseOver(getSelectorInput());
export const clearSelector = async () => click(getSelectorClear);
export const applyScopes = async () => {
await click(getSelectorApply);
diff --git a/public/app/features/scopes/tests/utils/assertions.ts b/public/app/features/scopes/tests/utils/assertions.ts
index 92e98715231..f3e031de7b4 100644
--- a/public/app/features/scopes/tests/utils/assertions.ts
+++ b/public/app/features/scopes/tests/utils/assertions.ts
@@ -12,7 +12,7 @@ import {
getResultApplicationsCloudSelect,
getResultApplicationsGrafanaSelect,
getResultApplicationsMimirSelect,
- getSelectorInput,
+ getSelectorInput as getSelectorButton,
getTreeHeadline,
queryAllDashboard,
queryDashboard,
@@ -45,8 +45,8 @@ export const expectRecentScope = (scope: string) => expectInDocument(() => getRe
export const expectRecentScopeNotPresentInDocument = () => expectNotInDocument(queryRecentScopesSection);
export const expectRecentScopesSection = () => expectInDocument(getRecentScopesSection);
export const expectScopesSelectorClosed = () => expectNotInDocument(querySelectorApply);
-export const expectScopesSelectorDisabled = () => expectDisabled(getSelectorInput);
-export const expectScopesSelectorValue = (value: string) => expectValue(getSelectorInput, value);
+export const expectScopesSelectorDisabled = () => expectDisabled(getSelectorButton);
+export const expectScopesSelectorValue = (value: string) => expect(getSelectorButton().dataset.value).toBe(value);
export const expectScopesHeadline = (value: string) => expectTextContent(getTreeHeadline, value);
export const expectPersistedApplicationsGrafanaNotPresent = () =>
expectNotInDocument(queryPersistedApplicationsGrafanaSelect);
diff --git a/public/app/features/scopes/tests/utils/selectors.ts b/public/app/features/scopes/tests/utils/selectors.ts
index 4e3b7ed109a..173198f7e59 100644
--- a/public/app/features/scopes/tests/utils/selectors.ts
+++ b/public/app/features/scopes/tests/utils/selectors.ts
@@ -35,7 +35,7 @@ const selectors = {
},
};
-export const getSelectorInput = () => screen.getByTestId(selectors.selector.input);
+export const getSelectorInput = () => screen.getByTestId(selectors.selector.input);
export const getSelectorClear = () => screen.getByTestId(selectors.selector.clear);
export const querySelectorApply = () => screen.queryByTestId(selectors.selector.apply);
export const getSelectorApply = () => screen.getByTestId(selectors.selector.apply);
diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json
index e2efd17b56c..305c35ceb3e 100644
--- a/public/locales/en-US/grafana.json
+++ b/public/locales/en-US/grafana.json
@@ -12413,8 +12413,10 @@
"cancel": "Cancel",
"error-title": "An unexpected error happened",
"input": {
- "placeholder": "Select scopes...",
- "removeAll": "Remove all scopes"
+ "placeholder": "No scopes",
+ "remove-all": "Remove all",
+ "removeAll": "Remove all scopes",
+ "tooltip": "Select scope"
},
"title": "Select scopes"
},