Accessibility: Make either `tooltip` or `aria-label` required when no button children present (#109131)

* ensure button must have tooltip/aria-label when no children

* fix violations

* restore some unnecessarily removed labels

* use types instead of interfaces as before

* further fixes

* fix unit test

* commit translations and fix unit test

* upgrade plugin-ui to 0.10.8

* set aria-label + unit tests
This commit is contained in:
Ashley Harrison 2025-08-05 12:04:35 +01:00 committed by GitHub
parent c7f3c92b21
commit 1021f05e32
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
72 changed files with 367 additions and 159 deletions

View File

@ -41,7 +41,7 @@ module.exports = {
verbose: false,
testEnvironment: 'jsdom',
transform: {
'^.+\\.(ts|tsx|js|jsx)$': [require.resolve('ts-jest'), { isolatedModules: true }],
'^.+\\.(ts|tsx|js|jsx)$': [require.resolve('ts-jest')],
},
transformIgnorePatterns: [
`/node_modules/(?!${esModules})`, // exclude es modules to prevent TS complaining

View File

@ -287,7 +287,7 @@
"@grafana/llm": "0.22.1",
"@grafana/monaco-logql": "^0.0.8",
"@grafana/o11y-ds-frontend": "workspace:*",
"@grafana/plugin-ui": "0.10.7",
"@grafana/plugin-ui": "0.10.8",
"@grafana/prometheus": "workspace:*",
"@grafana/runtime": "workspace:*",
"@grafana/scenes": "6.29.1",

View File

@ -20,7 +20,7 @@
"@emotion/css": "11.13.5",
"@grafana/data": "12.2.0-pre",
"@grafana/e2e-selectors": "12.2.0-pre",
"@grafana/plugin-ui": "0.10.7",
"@grafana/plugin-ui": "0.10.8",
"@grafana/runtime": "12.2.0-pre",
"@grafana/schema": "12.2.0-pre",
"@grafana/ui": "12.2.0-pre",

View File

@ -162,7 +162,7 @@ export function TraceToMetricsSettings({ options, onOptionsChange }: Props) {
<Button
variant="destructive"
title="Remove query"
aria-label="Remove query"
icon="times"
type="button"
onClick={() => {

View File

@ -44,7 +44,7 @@
"@grafana/data": "12.2.0-pre",
"@grafana/e2e-selectors": "12.2.0-pre",
"@grafana/i18n": "12.2.0-pre",
"@grafana/plugin-ui": "0.10.7",
"@grafana/plugin-ui": "0.10.8",
"@grafana/runtime": "12.2.0-pre",
"@grafana/schema": "12.2.0-pre",
"@grafana/ui": "12.2.0-pre",

View File

@ -188,7 +188,7 @@ export function ExemplarSetting({ value, onChange, onDelete, disabled }: Props)
>
<Button
variant="destructive"
title={t(
aria-label={t(
'grafana-prometheus.configuration.exemplar-setting.title-remove-exemplar-link',
'Remove exemplar link'
)}

View File

@ -119,7 +119,7 @@ export function OperationEditor({
fill="text"
icon="times"
variant="secondary"
title={t('grafana-prometheus.querybuilder.operation-editor.title-remove', 'Remove {{name}}', {
aria-label={t('grafana-prometheus.querybuilder.operation-editor.title-remove', 'Remove {{name}}', {
name: paramDef.name,
})}
onClick={() => onRemoveRestParam(paramIndex)}

View File

@ -55,7 +55,7 @@ export const OperationHeader = memo<Props>(
onClick={onToggleSwitcher}
fill="text"
variant="secondary"
title={t(
aria-label={t(
'grafana-prometheus.querybuilder.operation-header.title-click-to-view-alternative-operations',
'Click to view alternative operations'
)}
@ -67,7 +67,10 @@ export const OperationHeader = memo<Props>(
onClick={() => onRemove(index)}
fill="text"
variant="secondary"
title={t('grafana-prometheus.querybuilder.operation-header.title-remove-operation', 'Remove operation')}
aria-label={t(
'grafana-prometheus.querybuilder.operation-header.title-remove-operation',
'Remove operation'
)}
/>
</div>
</>

View File

@ -56,7 +56,7 @@ export const OperationInfoButton = memo<Props>(({ def, operation }) => {
return (
<>
<Button
title={t(
tooltip={t(
'grafana-prometheus.querybuilder.operation-info-button.title-click-to-show-description',
'Click to show description'
)}
@ -78,7 +78,7 @@ export const OperationInfoButton = memo<Props>(({ def, operation }) => {
onClick={() => setShow(false)}
fill="text"
variant="secondary"
title={t(
aria-label={t(
'grafana-prometheus.querybuilder.operation-info-button.title-remove-operation',
'Remove operation'
)}

View File

@ -39,7 +39,7 @@ describe('OperationList', () => {
it('removes an operation', async () => {
const { onChange } = setup();
const removeOperationButtons = screen.getAllByTitle('Remove operation');
const removeOperationButtons = screen.getAllByLabelText('Remove operation');
expect(removeOperationButtons).toHaveLength(2);
await userEvent.click(removeOperationButtons[1]);
expect(onChange).toHaveBeenCalledWith({

View File

@ -165,7 +165,7 @@ function SelectInputParamEditor({
fill="text"
icon="times"
variant="secondary"
title={t('grafana-prometheus.querybuilder.operation-param-editor.title-remove', 'Remove {{name}}', {
aria-label={t('grafana-prometheus.querybuilder.operation-param-editor.title-remove', 'Remove {{name}}', {
name: paramDef.name,
})}
onClick={() => onChange(index, '')}

View File

@ -19,7 +19,7 @@
"@grafana/data": "12.2.0-pre",
"@grafana/e2e-selectors": "12.2.0-pre",
"@grafana/i18n": "12.2.0-pre",
"@grafana/plugin-ui": "0.10.7",
"@grafana/plugin-ui": "0.10.8",
"@grafana/runtime": "12.2.0-pre",
"@grafana/ui": "12.2.0-pre",
"@react-awesome-query-builder/ui": "6.6.15",

View File

@ -165,7 +165,7 @@ export const settings: Settings = {
return (
<Button
type="button"
title={t('grafana-sql.components.settings.title-button-filter', '{{ buttonLabel }} filter', {
aria-label={t('grafana-sql.components.settings.title-button-filter', '{{ buttonLabel }} filter', {
buttonLabel: buttonProps?.label,
})}
onClick={buttonProps?.onClick}

View File

@ -53,7 +53,7 @@ function makeRenderColumn({ options }: { options?: Array<SelectableValue<string>
onChange={({ value }) => value && onChangeItem(setGroupByField(value))}
/>
<AccessoryButton
title={t(
aria-label={t(
'grafana-sql.components.make-render-column.render-column.title-remove-group-by-column',
'Remove group by column'
)}

View File

@ -94,7 +94,7 @@ export function SelectCustomFunctionParameters({
data-testid={selectors.components.SQLQueryEditor.selectInputParameter}
addonAfter={
<Button
title={t(
aria-label={t(
'grafana-sql.components.select-custom-function-parameters.render-parameters.params.title-remove-parameter',
'Remove parameter'
)}
@ -127,7 +127,7 @@ export function SelectCustomFunctionParameters({
variant="secondary"
size="md"
icon="plus"
title={t('grafana-sql.components.select-custom-function-parameters.title-add-parameter', 'Add parameter')}
aria-label={t('grafana-sql.components.select-custom-function-parameters.title-add-parameter', 'Add parameter')}
/>
<InlineLabel className={styles.label}>)</InlineLabel>
</>

View File

@ -153,7 +153,7 @@ export function SelectRow({ query, onQueryChange, db, columns }: SelectRowProps)
/>
</EditorField>
<Button
title={t('grafana-sql.components.select-row.title-remove-column', 'Remove column')}
aria-label={t('grafana-sql.components.select-row.title-remove-column', 'Remove column')}
type="button"
icon="trash-alt"
variant="secondary"
@ -167,7 +167,7 @@ export function SelectRow({ query, onQueryChange, db, columns }: SelectRowProps)
type="button"
onClick={addColumn}
variant="secondary"
title={t('grafana-sql.components.select-row.title-add-column', 'Add column')}
aria-label={t('grafana-sql.components.select-row.title-add-column', 'Add column')}
size="md"
icon="plus"
className={styles.addButton}

View File

@ -9,7 +9,7 @@ import { ConfirmModal } from '../ConfirmModal/ConfirmModal';
import { VariablesInputModal } from './VariablesInputModal';
type ActionButtonProps = ButtonProps & {
type ActionButtonProps = Omit<ButtonProps, 'children'> & {
action: ActionModel<Field>;
};

View File

@ -41,4 +41,26 @@ describe('Button', () => {
const svgElement = document.querySelector('svg');
expect(svgElement).toBeInTheDocument();
});
it('should set an aria-label if there is a tooltip string but no children', () => {
setup(<Button tooltip="Tooltip text" />);
expect(screen.getByRole('button', { name: 'Tooltip text' })).toBeInTheDocument();
});
it('should not set an aria-label if there is a tooltip string but child text', () => {
setup(<Button tooltip="Tooltip text">Child text</Button>);
expect(screen.queryByRole('button', { name: 'Tooltip text' })).not.toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Child text' })).toBeInTheDocument();
});
it('should prioritise the aria-label if it is present', () => {
setup(
<Button aria-label="Aria label" tooltip="Tooltip text">
Child text
</Button>
);
expect(screen.queryByRole('button', { name: 'Child text' })).not.toBeInTheDocument();
expect(screen.queryByRole('button', { name: 'Tooltip text' })).not.toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Aria label' })).toBeInTheDocument();
});
});

View File

@ -18,28 +18,43 @@ export const allButtonVariants: ButtonVariant[] = ['primary', 'secondary', 'dest
export type ButtonFill = 'solid' | 'outline' | 'text';
export const allButtonFills: ButtonFill[] = ['solid', 'outline', 'text'];
type CommonProps = {
type BaseProps = {
size?: ComponentSize;
variant?: ButtonVariant;
fill?: ButtonFill;
icon?: IconName | React.ReactElement;
className?: string;
children?: React.ReactNode;
fullWidth?: boolean;
type?: string;
/** Tooltip content to display on hover */
tooltip?: PopoverContent;
/** Position of the tooltip */
tooltipPlacement?: TooltipPlacement;
/** Position of the icon */
iconPlacement?: 'left' | 'right';
};
// either aria-label or tooltip is required for buttons without children
type NoChildrenAriaLabel = BaseProps & {
children?: never;
'aria-label': string;
};
type NoChildrenTooltip = BaseProps & {
children?: never;
tooltip: PopoverContent;
tooltipPlacement?: TooltipPlacement;
};
type BasePropsWithChildren = BaseProps & {
children: React.ReactNode;
};
type CommonProps = BasePropsWithChildren | NoChildrenTooltip | NoChildrenAriaLabel;
export type ButtonProps = CommonProps & ButtonHTMLAttributes<HTMLButtonElement>;
export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
(
{
'aria-label': ariaLabel,
variant = 'primary',
size = 'md',
fill = 'solid',
@ -92,6 +107,7 @@ export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
aria-disabled={hasTooltip && disabled}
disabled={!hasTooltip && disabled}
ref={tooltip ? undefined : ref}
aria-label={ariaLabel ?? (!children && typeof tooltip === 'string' ? tooltip : undefined)}
>
{iconPlacement === 'left' && iconComponent}
{children && <span className={styles.content}>{children}</span>}
@ -113,13 +129,12 @@ export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
Button.displayName = 'Button';
export type ButtonLinkProps = CommonProps &
ButtonHTMLAttributes<HTMLButtonElement> &
AnchorHTMLAttributes<HTMLAnchorElement>;
export type ButtonLinkProps = ButtonProps & Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'aria-label'>;
export const LinkButton = React.forwardRef<HTMLAnchorElement, ButtonLinkProps>(
(
{
'aria-label': ariaLabel,
variant = 'primary',
size = 'md',
fill = 'solid',
@ -164,6 +179,7 @@ export const LinkButton = React.forwardRef<HTMLAnchorElement, ButtonLinkProps>(
tabIndex={disabled ? -1 : 0}
aria-disabled={disabled}
ref={tooltip ? undefined : ref}
aria-label={ariaLabel ?? (!children && typeof tooltip === 'string' ? tooltip : undefined)}
>
<IconRenderer icon={icon} size={size} className={styles.icon} />
{children && <span className={styles.content}>{children}</span>}

View File

@ -14,7 +14,7 @@ import { Icon } from '../Icon/Icon';
export interface ButtonCascaderProps {
options: CascaderOption[];
children?: string;
children: string;
icon?: IconName;
disabled?: boolean;
value?: string[];
@ -24,7 +24,7 @@ export interface ButtonCascaderProps {
onPopupVisibleChange?: (visible: boolean) => void;
className?: string;
variant?: ButtonProps['variant'];
buttonProps?: ButtonProps;
buttonProps?: Omit<ButtonProps, 'children'>;
hideDownIcon?: boolean;
}

View File

@ -19,10 +19,10 @@ const meta: Meta = {
},
};
interface StoryProps extends Partial<Props> {
type StoryProps = Partial<Props> & {
inputText: string;
buttonText: string;
}
};
export const ClipboardButton: StoryFn<StoryProps> = (args) => {
const shareUrl = 'https://grafana.com/d/abcDEF-34t';

View File

@ -10,14 +10,14 @@ import { Button, ButtonProps } from '../Button/Button';
import { Icon } from '../Icon/Icon';
import { InlineToast } from '../InlineToast/InlineToast';
export interface Props extends ButtonProps {
export type Props = ButtonProps & {
/** A function that returns text to be copied */
getText(): string;
/** Callback when the text has been successfully copied */
onClipboardCopy?(copiedText: string): void;
/** Callback when there was an error copying the text */
onClipboardError?(copiedText: string, error: unknown): void;
}
};
const SHOW_SUCCESS_DURATION = 2 * 1000;
@ -73,7 +73,6 @@ export function ClipboardButton({
onClick={copyTextCallback}
icon={icon}
variant={showCopySuccess ? 'success' : variant}
aria-label={showCopySuccess ? copiedText : undefined}
{...buttonProps}
className={cx(styles.button, showCopySuccess && styles.successButton, buttonProps.className)}
ref={buttonRef}

View File

@ -4,7 +4,7 @@ import { ButtonProps, Button } from '../Button/Button';
type DataLinkButtonProps = {
link: LinkModel<Field>;
buttonProps?: ButtonProps;
buttonProps?: Omit<ButtonProps, 'children'>;
};
/**

View File

@ -135,6 +135,7 @@ export const FilterPopup = ({
value={searchFilter}
/>
<Button
tooltip={t('grafana-ui.table.filter-popup-aria-label-match-case', 'Match case')}
variant="secondary"
style={{ color: matchCase ? theme.colors.text.link : theme.colors.text.disabled }}
onClick={() => {

View File

@ -28,7 +28,10 @@ export default class PageActionBar extends PureComponent<Props> {
placeholder = 'Search by name or type',
sortPicker,
} = this.props;
const linkProps: Parameters<typeof LinkButton>[0] = { href: linkButton?.href, disabled: linkButton?.disabled };
const linkProps: Omit<Parameters<typeof LinkButton>[0], 'children'> = {
href: linkButton?.href,
disabled: linkButton?.disabled,
};
if (target) {
linkProps.target = target;

View File

@ -4,7 +4,7 @@ import { Trans, t } from '@grafana/i18n';
import { Button, ButtonProps, Icon, Stack } from '@grafana/ui';
const MoreButton = forwardRef(function MoreButton(
props: ButtonProps & { title?: string },
props: Omit<ButtonProps, 'children'> & { title?: string },
ref: Ref<HTMLButtonElement>
) {
return (

View File

@ -1,6 +1,7 @@
import { css } from '@emotion/css';
import { GrafanaTheme2 } from '@grafana/data';
import { t } from '@grafana/i18n';
import { Icon, LinkButton, Stack, Tooltip, useStyles2 } from '@grafana/ui';
import { ReceiverPluginMetadata } from './useReceiversMetadata';
@ -25,7 +26,14 @@ export const ReceiverMetadataBadge = ({ metadata: { icon, title, externalUrl, wa
<span>{title}</span>
</Stack>
{externalUrl && (
<LinkButton icon="external-link-alt" href={externalUrl} target="_blank" variant="secondary" size="sm" />
<LinkButton
aria-label={t('alerting.receiver-metadata-badge.aria-label-open-external-link', 'Open external link')}
icon="external-link-alt"
href={externalUrl}
target="_blank"
variant="secondary"
size="sm"
/>
)}
</Stack>
);

View File

@ -301,7 +301,14 @@ export const Title = ({ name, paused = false, state, health, ruleType, ruleOrigi
return (
<Stack direction="row" gap={1} minWidth={0} alignItems="center">
{returnToHref && <LinkButton variant="secondary" icon="angle-left" href={returnTo} />}
{returnToHref && (
<LinkButton
aria-label={t('alerting.rule-viewer.aria-label-return-to', 'Return to previous view')}
variant="secondary"
icon="angle-left"
href={returnTo}
/>
)}
{ruleOrigin && <PluginOriginBadge pluginId={ruleOrigin.pluginId} size="lg" />}
<Text variant="h1" truncate>
{name}

View File

@ -5,9 +5,13 @@ import { Button, ButtonProps, useStyles2 } from '@grafana/ui';
type Props = Omit<ButtonProps, 'variant' | 'size'>;
export const ActionButton = ({ className, ...restProps }: Props) => {
export const ActionButton = ({ children, className, ...restProps }: Props) => {
const styles = useStyles2(getStyle);
return <Button variant="secondary" size="xs" className={cx(styles.wrapper, className)} {...restProps} />;
return (
<Button variant="secondary" size="xs" className={cx(styles.wrapper, className)} {...restProps}>
{children}
</Button>
);
};
export const getStyle = (theme: GrafanaTheme2) => ({

View File

@ -27,32 +27,32 @@ export const ActionIcon = ({
}: Props) => {
const ariaLabel = typeof tooltip === 'string' ? tooltip : undefined;
return (
<Tooltip content={tooltip} placement={tooltipPlacement}>
{to ? (
<LinkButton
variant="secondary"
fill="text"
icon={icon}
href={to}
size="sm"
target={target}
{...rest}
aria-label={ariaLabel}
/>
) : (
<Button
className={className}
variant="secondary"
fill="text"
size="sm"
icon={icon}
type="button"
onClick={onClick}
{...rest}
aria-label={ariaLabel}
/>
)}
</Tooltip>
return to ? (
<LinkButton
tooltip={tooltip}
tooltipPlacement={tooltipPlacement}
variant="secondary"
fill="text"
icon={icon}
href={to}
size="sm"
target={target}
{...rest}
aria-label={ariaLabel}
/>
) : (
<Button
tooltip={tooltip}
tooltipPlacement={tooltipPlacement}
className={className}
variant="secondary"
fill="text"
size="sm"
icon={icon}
type="button"
onClick={onClick}
{...rest}
aria-label={ariaLabel}
/>
);
};

View File

@ -1,3 +1,4 @@
import { t } from '@grafana/i18n';
import { LinkButton, Stack, Text } from '@grafana/ui';
import { useReturnTo } from '../hooks/useReturnTo';
@ -7,7 +8,12 @@ export const Title = ({ name }: { name: string }) => {
return (
<Stack direction="row" gap={1} minWidth={0} alignItems="center">
<LinkButton variant="secondary" icon="angle-left" href={returnTo} />
<LinkButton
aria-label={t('alerting.group-details.title.back', 'Back to alerting')}
variant="secondary"
icon="angle-left"
href={returnTo}
/>
<Text element="h1" truncate>
{name}
</Text>

View File

@ -21,17 +21,17 @@ export function RuleListPageTitle({ title }: { title: string }) {
window.location.reload();
};
const { text, ...configToUse }: ButtonProps & { text: string; 'data-testid': string } = listViewV2Enabled
const configToUse: ButtonProps & { 'data-testid': string } = listViewV2Enabled
? {
variant: 'secondary',
icon: undefined,
text: t('alerting.rule-list.toggle.go-back-to-old-look', 'Go back to the old look'),
children: t('alerting.rule-list.toggle.go-back-to-old-look', 'Go back to the old look'),
'data-testid': 'alerting-list-view-toggle-v1',
}
: {
variant: 'primary',
icon: 'rocket',
text: t('alerting.rule-list.toggle.try-out-the-new-look', 'Try out the new look!'),
children: t('alerting.rule-list.toggle.try-out-the-new-look', 'Try out the new look!'),
'data-testid': 'alerting-list-view-toggle-v2',
};
@ -40,9 +40,7 @@ export function RuleListPageTitle({ title }: { title: string }) {
<h1>{title}</h1>
{shouldShowV2Toggle && (
<div>
<Button size="sm" fill="outline" {...configToUse} onClick={toggleListView} className="fs-unmask">
{text}
</Button>
<Button size="sm" fill="outline" {...configToUse} onClick={toggleListView} className="fs-unmask" />
</div>
)}
</Stack>

View File

@ -96,7 +96,7 @@ export function PanelVizTypePicker({ panel, data, onChange, onClose }: Props) {
placeholder={t('dashboard-scene.panel-viz-type-picker.placeholder-search-for', 'Search for...')}
/>
<Button
title={t('dashboard-scene.panel-viz-type-picker.title-close', 'Close')}
aria-label={t('dashboard-scene.panel-viz-type-picker.title-close', 'Close')}
variant="secondary"
icon="angle-up"
className={styles.closeButton}

View File

@ -5,6 +5,7 @@ import { useLocalStorage } from 'react-use';
import { GrafanaTheme2 } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { t } from '@grafana/i18n';
import { Button, Counter, Icon, Tooltip, useStyles2 } from '@grafana/ui';
import { useQueryParams } from 'app/core/hooks/useQueryParams';
@ -139,6 +140,11 @@ export const OptionsPaneCategory = React.memo(
{renderTitle(isExpanded)}
</h6>
<Button
aria-label={
isExpanded
? t('dashboard.options-pane-category.aria-label-collapse', 'Collapse {{title}} category', { title })
: t('dashboard.options-pane-category.aria-label-expand', 'Expand {{title}} category', { title })
}
data-testid={selectors.components.OptionsGroup.toggle(id)}
type="button"
fill="text"

View File

@ -3,7 +3,7 @@ import { useRef } from 'react';
import * as React from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { Trans } from '@grafana/i18n';
import { t, Trans } from '@grafana/i18n';
import {
Button,
InlineField,
@ -86,7 +86,16 @@ export const ResourcePicker = (props: Props) => {
placeholder={placeholder}
readOnly={true}
prefix={sanitizedSrc && <SanitizedSVG src={sanitizedSrc} className={styles.icon} style={{ ...colorStyle }} />}
suffix={<Button icon="times" variant="secondary" fill="text" size="sm" onClick={onClear} />}
suffix={
<Button
aria-label={t('dimensions.resource-picker.aria-label-clear-value', 'Clear value')}
icon="times"
variant="secondary"
fill="text"
size="sm"
onClick={onClear}
/>
}
/>
</InlineField>
</InlineFieldRow>

View File

@ -115,7 +115,16 @@ export const TextDimensionEditor = ({ value, context, onChange }: Props) => {
onChange={onFixedChange}
item={dummyStringSettings}
suffix={
value?.fixed && <Button icon="times" variant="secondary" fill="text" size="sm" onClick={onClearFixed} />
value?.fixed && (
<Button
aria-label={t('dimensions.text-dimension-editor.aria-label-clear-value', 'Clear value')}
icon="times"
variant="secondary"
fill="text"
size="sm"
onClick={onClearFixed}
/>
)
}
/>
</InlineField>

View File

@ -92,6 +92,10 @@ export function ContentOutlineItemButton({
</button>
{onRemove && (
<Button
aria-label={t(
'explore.content-outline-item-button.body.aria-label-content-outline-item-delete-button',
'Delete item'
)}
variant="destructive"
className={styles.deleteButton}
icon="times"

View File

@ -218,7 +218,14 @@ export const LogsVolumePanelList = ({
label={t('explore.logs-volume-panel-list.label-reload-log-volume', 'Reload log volume')}
transparent
>
<Button size="xs" icon="sync" variant="secondary" onClick={onLoadLogsVolume} id="reload-volume" />
<Button
aria-label={t('explore.logs-volume-panel-list.aria-label-reload-log-volume', 'Reload log volume')}
size="xs"
icon="sync"
variant="secondary"
onClick={onLoadLogsVolume}
id="reload-volume"
/>
</InlineField>
</div>
)}

View File

@ -249,7 +249,13 @@ export const TracePageHeader = memo((props: TracePageHeaderProps) => {
</Tooltip>
<Dropdown overlay={shareDropdownMenu} placement="bottom-end">
<Button size="sm" variant="secondary" fill="outline" icon="angle-down" />
<Button
aria-label={t('explore.trace-page-header.aria-label-share-dropdown', 'Open share trace options menu')}
size="sm"
variant="secondary"
fill="outline"
icon="angle-down"
/>
</Dropdown>
</ButtonGroup>
</div>

View File

@ -51,7 +51,13 @@ export default function CopyIcon({ copyText, icon = 'copy', tooltipTitle }: Prop
return (
<Tooltip content={hasCopied ? t('explore.trace-view.tooltip-copy-icon', 'Copied') : tooltipTitle}>
<Button className={cx(styles.CopyIcon)} type="button" icon={icon} onClick={handleClick} />
<Button
aria-label={t('explore.trace-view.aria-label-copy', 'Copy to clipboard')}
className={cx(styles.CopyIcon)}
type="button"
icon={icon}
onClick={handleClick}
/>
</Tooltip>
);
}

View File

@ -143,7 +143,7 @@ describe('LogDetailsRow', () => {
it('should be invisible unless mouse is over', () => {
setup({ parsedValues: ['test value'] });
// This tests a regression where the button was always visible.
expect(screen.getByTitle('Copy value to clipboard')).not.toBeVisible();
expect(screen.getByLabelText('Copy value to clipboard')).not.toBeVisible();
// Asserting visibility on mouse-over is currently not possible.
});
});

View File

@ -250,7 +250,7 @@ class UnThemedLogDetailsRow extends PureComponent<Props, State> {
<div className={`log-details-value-copy ${styles.copyButton}`}>
<ClipboardButton
getText={() => val}
title={t('logs.un-themed-log-details-row.title-copy-value-to-clipboard', 'Copy value to clipboard')}
aria-label={t('logs.un-themed-log-details-row.title-copy-value-to-clipboard', 'Copy value to clipboard')}
fill="text"
variant="secondary"
icon="copy"

View File

@ -432,7 +432,7 @@ const ClipboardButtonWrapper = ({ value }: { value: string }) => {
<div className={styles.button}>
<ClipboardButton
getText={() => value}
title={t('logs.log-line-details.fields.copy-value-to-clipboard', 'Copy value to clipboard')}
aria-label={t('logs.log-line-details.fields.copy-value-to-clipboard', 'Copy value to clipboard')}
fill="text"
variant="secondary"
icon="copy"

View File

@ -1,6 +1,7 @@
import { css } from '@emotion/css';
import { GrafanaTheme2 } from '@grafana/data';
import { t } from '@grafana/i18n';
import { ClipboardButton, useStyles2, CodeEditor } from '@grafana/ui';
interface Props {
@ -16,7 +17,14 @@ export const CodeBlock = ({ code, copyCode = true }: Props) => {
return (
<div className={styles.container}>
{copyCode && (
<ClipboardButton className={styles.copyButton} variant="secondary" size="sm" icon="copy" getText={() => code} />
<ClipboardButton
aria-label={t('provisioning.code-block.aria-label-copy', 'Copy code to clipboard')}
className={styles.copyButton}
variant="secondary"
size="sm"
icon="copy"
getText={() => code}
/>
)}
<CodeEditor
value={code}

View File

@ -431,7 +431,7 @@ export function QueryGroupTopSection({
<Button
variant="secondary"
icon="question-circle"
title={t(
tooltip={t(
'query.query-group-top-section.query-tab-help-button-title-open-data-source-help',
'Open data source help'
)}

View File

@ -99,7 +99,12 @@ export const FilterByValueFilterEditor = (props: Props) => {
<editor.component field={field} options={filter.config.options ?? {}} onChange={onChangeMatcherOptions} />
</InlineField>
<Box marginBottom={0.5}>
<Button icon="times" onClick={onDelete} variant="secondary" />
<Button
aria-label={t('transformers.filter-by-value-filter-editor.aria-label-remove-filter', 'Remove filter')}
icon="times"
onClick={onDelete}
variant="secondary"
/>
</Box>
</InlineFieldRow>
);

View File

@ -65,7 +65,12 @@ export const FilterItem: React.FC<FilterItemProps> = ({
width={inputFieldSize}
disabled={!filter.property?.name}
/>
<Button variant="secondary" icon="times" onClick={() => onDelete(groupIndex, filterIndex)} />
<Button
aria-label={t('components.filter-item.aria-label-remove-filter', 'Remove filter')}
variant="secondary"
icon="times"
onClick={() => onDelete(groupIndex, filterIndex)}
/>
{showOr && (
<Label style={{ padding: '9px 14px' }}>
<Trans i18nKey="components.filter-item.label-or">OR</Trans>

View File

@ -219,7 +219,12 @@ export const FilterSection: React.FC<FilterSectionProps> = ({
<div className={styles.filters}>
{filters.length === 0 || filters.every((g) => g.expressions.length === 0) ? (
<InputGroup>
<Button variant="secondary" onClick={onAddAndFilters} icon="plus" />
<Button
aria-label={t('components.filter-section.aria-label-add-filter', 'Add filter')}
variant="secondary"
onClick={onAddAndFilters}
icon="plus"
/>
</InputGroup>
) : (
<>
@ -248,6 +253,7 @@ export const FilterSection: React.FC<FilterSectionProps> = ({
))}
</>
<Button
tooltip={t('components.filter-section.aria-label-add-or-filter', 'Add OR filter')}
variant="secondary"
style={{ marginLeft: '15px' }}
onClick={() => onAddOrFilters(groupIndex, 'property', '')}

View File

@ -126,10 +126,20 @@ export const FuzzySearch: React.FC<FuzzySearchProps> = ({
onChange={(e: SelectableValue<string>) => updateFuzzySearch(e.value ?? '*', searchTerm)}
width="auto"
/>
<Button variant="secondary" icon="times" onClick={onDeleteFuzzySearch} />
<Button
aria-label={t('components.fuzzy-search.aria-label-remove-fuzzy-search', 'Remove fuzzy search')}
variant="secondary"
icon="times"
onClick={onDeleteFuzzySearch}
/>
</>
) : (
<Button variant="secondary" onClick={() => setIsOpen(true)} icon="plus" />
<Button
aria-label={t('components.fuzzy-search.aria-label-add-fuzzy-search', 'Add fuzzy search')}
variant="secondary"
onClick={() => setIsOpen(true)}
icon="plus"
/>
)}
</InputGroup>
</EditorField>

View File

@ -106,6 +106,7 @@ export const GroupBySection: React.FC<GroupBySectionProps> = ({
/>
) : (
<Button
aria-label={t('components.group-by-section.aria-label-add-group-by', 'Add group by')}
variant="secondary"
icon="plus"
onClick={() =>

View File

@ -139,9 +139,15 @@ export const OrderBySection: React.FC<OrderBySectionProps> = ({ query, allColumn
options={orderOptions}
onChange={(e) => e.value && handleOrderByChange(index, 'order', e.value)}
/>
<Button variant="secondary" icon="times" onClick={() => onDeleteOrderBy(index)} />
<Button
aria-label={t('components.order-by-section.aria-label-remove-order-by', 'Remove order by')}
variant="secondary"
icon="times"
onClick={() => onDeleteOrderBy(index)}
/>
{index === orderBy.length - 1 ? (
<Button
aria-label={t('components.order-by-section.aria-label-add-order-by', 'Add order by')}
variant="secondary"
onClick={() => handleOrderByChange(-1, 'column', '')}
icon="plus"
@ -154,7 +160,12 @@ export const OrderBySection: React.FC<OrderBySectionProps> = ({ query, allColumn
))
) : (
<InputGroup>
<Button variant="secondary" onClick={() => handleOrderByChange(-1, 'column', '')} icon="plus" />
<Button
aria-label={t('components.order-by-section.aria-label-add-order-by', 'Add order by')}
variant="secondary"
onClick={() => handleOrderByChange(-1, 'column', '')}
icon="plus"
/>
</InputGroup>
)}
</>

View File

@ -163,7 +163,12 @@ export const TableSection: React.FC<TableSectionProps> = (props) => {
isDisabled={!builderQuery?.from?.property.name}
width={30}
/>
<Button variant="secondary" icon="times" onClick={onDeleteAllColumns} />
<Button
tooltip={t('components.table-section.tooltip-remove-all-columns', 'Remove all columns')}
variant="secondary"
icon="times"
onClick={onDeleteAllColumns}
/>
</InputGroup>
</EditorField>
</EditorFieldGroup>

View File

@ -115,9 +115,12 @@
"aria-label-column": "Column",
"aria-label-column-value": "Column value",
"aria-label-operator": "Operator",
"aria-label-remove-filter": "Remove filter",
"label-or": "OR"
},
"filter-section": {
"aria-label-add-filter": "Add filter",
"aria-label-add-or-filter": "Add OR filter",
"label-add-group": "Add group",
"label-and": "AND",
"label-filters": "Filters",
@ -130,6 +133,8 @@
"label-format-as": "Format as"
},
"fuzzy-search": {
"aria-label-add-fuzzy-search": "Add fuzzy search",
"aria-label-remove-fuzzy-search": "Remove fuzzy search",
"aria-label-select-column": "Select Column",
"label-fuzzy-search": "Fuzzy Search",
"placeholder-search-team": "Enter search term",
@ -144,6 +149,7 @@
"aria-label-remove": "Remove"
},
"group-by-section": {
"aria-label-add-group-by": "Add group by",
"label-group-by": "Group by",
"tooltip-group-by": "Organize results into categories based on specified columns. Group by can be used independently to list unique values in selected columns, or combined with aggregate functions to produce summary statistics for each group. When used alone, it returns distinct combinations of the specified columns."
},
@ -186,8 +192,10 @@
"text-loading": "Loading..."
},
"order-by-section": {
"aria-label-add-order-by": "Add order by",
"aria-label-order-by-column": "Order by column",
"aria-label-order-direction": "Order Direction",
"aria-label-remove-order-by": "Remove order by",
"label-by": "BY",
"label-order-by": "Order By",
"tooltip-order-by": "Sort results based on one or more columns in ascending or descending order."
@ -249,7 +257,8 @@
"label-columns": "Columns",
"label-table": "Table",
"placeholder-select-columns": "Select columns",
"placeholder-select-table": "Select a table"
"placeholder-select-table": "Select a table",
"tooltip-remove-all-columns": "Remove all columns"
},
"time-grain-field": {
"label-time-grain": "Time grain"

View File

@ -7,7 +7,7 @@
"@emotion/css": "11.13.5",
"@grafana/data": "12.2.0-pre",
"@grafana/i18n": "12.2.0-pre",
"@grafana/plugin-ui": "0.10.7",
"@grafana/plugin-ui": "0.10.8",
"@grafana/runtime": "12.2.0-pre",
"@grafana/schema": "12.2.0-pre",
"@grafana/ui": "12.2.0-pre",

View File

@ -7,7 +7,7 @@
"@emotion/css": "11.13.5",
"@grafana/data": "12.2.0-pre",
"@grafana/google-sdk": "0.3.4",
"@grafana/plugin-ui": "0.10.7",
"@grafana/plugin-ui": "0.10.8",
"@grafana/runtime": "12.2.0-pre",
"@grafana/schema": "12.2.0-pre",
"@grafana/ui": "12.2.0-pre",

View File

@ -57,7 +57,7 @@ export const DataLink = (props: Props) => {
</InlineField>
<Button
variant={'destructive'}
title="Remove field"
aria-label="Remove field"
icon="times"
onClick={(event) => {
event.preventDefault();

View File

@ -6,7 +6,7 @@
"dependencies": {
"@emotion/css": "11.13.5",
"@grafana/data": "12.2.0-pre",
"@grafana/plugin-ui": "0.10.7",
"@grafana/plugin-ui": "0.10.8",
"@grafana/runtime": "12.2.0-pre",
"@grafana/sql": "12.2.0-pre",
"@grafana/ui": "12.2.0-pre",

View File

@ -83,7 +83,12 @@ const CSVWaveEditor = (props: WaveProps) => {
onBlur={() => onValueChange('labels', labels)}
/>
</InlineField>
<Button icon={last ? 'plus' : 'minus'} variant="secondary" onClick={onAction} />
<Button
aria-label={last ? 'Add wave' : 'Remove wave'}
icon={last ? 'plus' : 'minus'}
variant="secondary"
onClick={onAction}
/>
</InlineFieldRow>
);
};

View File

@ -8,7 +8,7 @@
"@grafana/data": "workspace:*",
"@grafana/e2e-selectors": "workspace:*",
"@grafana/o11y-ds-frontend": "workspace:*",
"@grafana/plugin-ui": "0.10.7",
"@grafana/plugin-ui": "0.10.8",
"@grafana/runtime": "workspace:*",
"@grafana/ui": "workspace:*",
"lodash": "4.17.21",

View File

@ -269,32 +269,31 @@ export function LokiContextUi(props: LokiContextUiProps) {
elevated={true}
></Alert>
)}
<Tooltip content={'Revert to initial log context query.'}>
<div className={styles.iconButton}>
<Button
data-testid="revert-button"
icon="history-alt"
variant="secondary"
disabled={isInitialState}
onClick={(e) => {
reportInteraction('grafana_explore_logs_loki_log_context_reverted', {
logRowUid: row.uid,
});
setContextFilters((contextFilters) => {
return contextFilters.map((contextFilter) => ({
...contextFilter,
// For revert to initial query we need to enable all labels and disable all parsed labels
enabled: !contextFilter.nonIndexed,
}));
});
// We are removing the preserved labels from local storage so we can preselect the labels in the UI
window.localStorage.removeItem(LOKI_LOG_CONTEXT_PRESERVED_LABELS);
window.localStorage.removeItem(SHOULD_INCLUDE_PIPELINE_OPERATIONS);
setIncludePipelineOperations(false);
}}
/>
</div>
</Tooltip>
<div className={styles.iconButton}>
<Button
tooltip="Revert to initial log context query"
data-testid="revert-button"
icon="history-alt"
variant="secondary"
disabled={isInitialState}
onClick={(e) => {
reportInteraction('grafana_explore_logs_loki_log_context_reverted', {
logRowUid: row.uid,
});
setContextFilters((contextFilters) => {
return contextFilters.map((contextFilter) => ({
...contextFilter,
// For revert to initial query we need to enable all labels and disable all parsed labels
enabled: !contextFilter.nonIndexed,
}));
});
// We are removing the preserved labels from local storage so we can preselect the labels in the UI
window.localStorage.removeItem(LOKI_LOG_CONTEXT_PRESERVED_LABELS);
window.localStorage.removeItem(SHOULD_INCLUDE_PIPELINE_OPERATIONS);
setIncludePipelineOperations(false);
}}
/>
</div>
<Collapse
collapsible={true}

View File

@ -132,7 +132,7 @@ export const DerivedField = (props: Props) => {
<Field label="">
<Button
variant="destructive"
title="Remove field"
aria-label="Remove field"
icon="times"
onClick={(event) => {
event.preventDefault();

View File

@ -44,7 +44,7 @@ describe('DerivedFields', () => {
const onChange = jest.fn();
render(<DerivedFields fields={testFields} onChange={onChange} />);
await userEvent.click((await screen.findAllByTitle('Remove field'))[0]);
await userEvent.click((await screen.findAllByLabelText('Remove field'))[0]);
await waitFor(() => expect(onChange).toHaveBeenCalledWith([testFields[1]]));
});

View File

@ -7,7 +7,7 @@
"@emotion/css": "11.13.5",
"@grafana/data": "12.2.0-pre",
"@grafana/i18n": "12.2.0-pre",
"@grafana/plugin-ui": "0.10.7",
"@grafana/plugin-ui": "0.10.8",
"@grafana/runtime": "12.2.0-pre",
"@grafana/sql": "12.2.0-pre",
"@grafana/ui": "12.2.0-pre",

View File

@ -6,7 +6,7 @@
"dependencies": {
"@emotion/css": "11.13.5",
"@grafana/data": "12.2.0-pre",
"@grafana/plugin-ui": "0.10.7",
"@grafana/plugin-ui": "0.10.8",
"@grafana/runtime": "12.2.0-pre",
"@grafana/sql": "12.2.0-pre",
"@grafana/ui": "12.2.0-pre",

View File

@ -10,7 +10,7 @@
"@grafana/lezer-traceql": "0.0.23",
"@grafana/monaco-logql": "^0.0.8",
"@grafana/o11y-ds-frontend": "workspace:*",
"@grafana/plugin-ui": "0.10.7",
"@grafana/plugin-ui": "0.10.8",
"@grafana/runtime": "workspace:*",
"@grafana/schema": "workspace:*",
"@grafana/ui": "workspace:*",

View File

@ -8,7 +8,7 @@
"@grafana/data": "workspace:*",
"@grafana/e2e-selectors": "workspace:*",
"@grafana/o11y-ds-frontend": "workspace:*",
"@grafana/plugin-ui": "0.10.7",
"@grafana/plugin-ui": "0.10.8",
"@grafana/runtime": "workspace:*",
"@grafana/ui": "workspace:*",
"lodash": "4.17.21",

View File

@ -84,7 +84,14 @@ export const MeasureOverlay = ({ map, menuActiveState }: Props) => {
vector.current.addInteraction(map, m.geometry, showSegments, clearPrevious);
}}
/>
<Button className={measureStyle.button} icon="times" variant="secondary" size="sm" onClick={toggleMenu} />
<Button
aria-label={t('geomap.measure-overlay.aria-label-close', 'Close measure tools')}
className={measureStyle.button}
icon="times"
variant="secondary"
size="sm"
onClick={toggleMenu}
/>
</div>
<Select
className={measureStyle.unitSelect}

View File

@ -40,8 +40,8 @@ describe('NodeGraph', () => {
layoutAlgorithm={LayoutAlgorithm.Force}
/>
);
const zoomIn = await screen.findByTitle(/Zoom in/);
const zoomOut = await screen.findByTitle(/Zoom out/);
const zoomIn = await screen.findByLabelText(/Zoom in/);
const zoomOut = await screen.findByLabelText(/Zoom out/);
expect(getScale()).toBe(1);
await userEvent.click(zoomIn);

View File

@ -42,7 +42,7 @@ export function ViewControls<Config extends Record<string, any>>(props: Props<Co
icon={'plus-circle'}
onClick={onPlus}
size={'md'}
title={t('nodeGraph.view-controls.title-zoom-in', 'Zoom in')}
aria-label={t('nodeGraph.view-controls.title-zoom-in', 'Zoom in')}
variant="secondary"
disabled={disableZoomIn}
/>
@ -50,7 +50,7 @@ export function ViewControls<Config extends Record<string, any>>(props: Props<Co
icon={'minus-circle'}
onClick={onMinus}
size={'md'}
title={t('nodeGraph.view-controls.title-zoom-out', 'Zoom out')}
aria-label={t('nodeGraph.view-controls.title-zoom-out', 'Zoom out')}
variant="secondary"
disabled={disableZoomOut}
/>

View File

@ -60,7 +60,7 @@ export const ArcOptionsEditor = ({ value, onChange, context }: ArcOptionsEditorP
icon="minus"
variant="secondary"
onClick={() => removeArc(i)}
title={t('nodeGraph.arc-options-editor.title-remove-arc', 'Remove arc')}
aria-label={t('nodeGraph.arc-options-editor.title-remove-arc', 'Remove arc')}
/>
</div>
);

View File

@ -1523,7 +1523,10 @@
"group-loading-error": "Error loading the group",
"interval": "Interval",
"namespace": "Namespace",
"new": "New"
"new": "New",
"title": {
"back": "Back to alerting"
}
},
"group-edit": {
"ds-error": "Error loading data source details",
@ -2240,6 +2243,9 @@
"title-attention": "Attention",
"title-manage-contact-point-permissions": "Manage contact point permissions"
},
"receiver-metadata-badge": {
"aria-label-open-external-link": "Open external link"
},
"receivers-section": {
"button-more": "More",
"new-menu": {
@ -2566,6 +2572,7 @@
}
},
"rule-viewer": {
"aria-label-return-to": "Return to previous view",
"error-loading": "Something went wrong loading the rule",
"evaluation-interval": "Every {{interval}}",
"prometheus-consistency-check": {
@ -4944,6 +4951,10 @@
"description": "Description",
"title-option": "Title"
},
"options-pane-category": {
"aria-label-collapse": "Collapse {{title}} category",
"aria-label-expand": "Expand {{title}} category"
},
"options-pane-options": {
"placeholder-search-options": "Search options",
"Recent options-title-recent-options": "Recent options",
@ -6598,6 +6609,7 @@
"label-source": "Source"
},
"resource-picker": {
"aria-label-clear-value": "Clear value",
"render-small-resource-picker": {
"set-icon": "Set icon"
}
@ -6634,6 +6646,7 @@
"noOptionsMessage-no-fields-found": "No fields found"
},
"text-dimension-editor": {
"aria-label-clear-value": "Clear value",
"description-field": "Display field value",
"description-fixed": "Fixed value",
"label-field": "Field",
@ -6771,7 +6784,8 @@
},
"content-outline-item-button": {
"body": {
"aria-label-content-outline-item-collapse-button": "Content outline item collapse button"
"aria-label-content-outline-item-collapse-button": "Content outline item collapse button",
"aria-label-content-outline-item-delete-button": "Delete item"
}
},
"correlation-editor-mode-bar": {
@ -6981,6 +6995,7 @@
"content-streaming": "Streaming"
},
"logs-volume-panel-list": {
"aria-label-reload-log-volume": "Reload log volume",
"label-reload-log-volume": "Reload log volume",
"loading": "Loading...",
"title-failed-volume-query": "Failed to load log volume for this query",
@ -7279,6 +7294,7 @@
"split-widen": "Widen pane"
},
"trace-page-header": {
"aria-label-share-dropdown": "Open share trace options menu",
"duration": "Duration",
"export-started": "Export started",
"give-feedback": "Feedback",
@ -7303,6 +7319,7 @@
"label-show-paths": "Show critical path only switch"
},
"trace-view": {
"aria-label-copy": "Copy to clipboard",
"no-data": "No data",
"tooltip-copy-icon": "Copied"
},
@ -7607,6 +7624,7 @@
"title-symbol": "Symbol"
},
"measure-overlay": {
"aria-label-close": "Close measure tools",
"tooltip-show-measure-tools": "Show measure tools"
},
"name-initial-view": "Initial view",
@ -8639,6 +8657,7 @@
"csv-placeholder": "Enter CSV here...",
"filter-placeholder": "Filter values",
"filter-popup-apply": "Ok",
"filter-popup-aria-label-match-case": "Match case",
"filter-popup-cancel": "Cancel",
"filter-popup-clear": "Clear filter",
"filter-popup-heading": "Filter by values:",
@ -11066,6 +11085,9 @@
"check-repository": {
"check": "Check"
},
"code-block": {
"aria-label-copy": "Copy code to clipboard"
},
"config-form": {
"alert-repository-settings-saved": "Repository settings saved",
"alert-repository-settings-updated": "Repository settings updated",
@ -13121,6 +13143,7 @@
}
},
"filter-by-value-filter-editor": {
"aria-label-remove-filter": "Remove filter",
"label-field": "Field",
"label-match": "Match",
"label-value": "Value",

View File

@ -2542,7 +2542,7 @@ __metadata:
"@grafana/e2e-selectors": "npm:12.2.0-pre"
"@grafana/i18n": "npm:12.2.0-pre"
"@grafana/plugin-configs": "npm:12.2.0-pre"
"@grafana/plugin-ui": "npm:0.10.7"
"@grafana/plugin-ui": "npm:0.10.8"
"@grafana/runtime": "npm:12.2.0-pre"
"@grafana/schema": "npm:12.2.0-pre"
"@grafana/ui": "npm:12.2.0-pre"
@ -2588,7 +2588,7 @@ __metadata:
"@grafana/data": "npm:12.2.0-pre"
"@grafana/e2e-selectors": "npm:12.2.0-pre"
"@grafana/plugin-configs": "npm:12.2.0-pre"
"@grafana/plugin-ui": "npm:0.10.7"
"@grafana/plugin-ui": "npm:0.10.8"
"@grafana/runtime": "npm:12.2.0-pre"
"@grafana/sql": "npm:12.2.0-pre"
"@grafana/ui": "npm:12.2.0-pre"
@ -2702,7 +2702,7 @@ __metadata:
"@grafana/e2e-selectors": "workspace:*"
"@grafana/o11y-ds-frontend": "workspace:*"
"@grafana/plugin-configs": "workspace:*"
"@grafana/plugin-ui": "npm:0.10.7"
"@grafana/plugin-ui": "npm:0.10.8"
"@grafana/runtime": "workspace:*"
"@grafana/ui": "workspace:*"
"@testing-library/dom": "npm:10.4.1"
@ -2789,7 +2789,7 @@ __metadata:
"@grafana/e2e-selectors": "npm:12.2.0-pre"
"@grafana/i18n": "npm:12.2.0-pre"
"@grafana/plugin-configs": "npm:12.2.0-pre"
"@grafana/plugin-ui": "npm:0.10.7"
"@grafana/plugin-ui": "npm:0.10.8"
"@grafana/runtime": "npm:12.2.0-pre"
"@grafana/sql": "npm:12.2.0-pre"
"@grafana/ui": "npm:12.2.0-pre"
@ -2821,7 +2821,7 @@ __metadata:
"@grafana/data": "npm:12.2.0-pre"
"@grafana/e2e-selectors": "npm:12.2.0-pre"
"@grafana/plugin-configs": "npm:12.2.0-pre"
"@grafana/plugin-ui": "npm:0.10.7"
"@grafana/plugin-ui": "npm:0.10.8"
"@grafana/runtime": "npm:12.2.0-pre"
"@grafana/sql": "npm:12.2.0-pre"
"@grafana/ui": "npm:12.2.0-pre"
@ -2887,7 +2887,7 @@ __metadata:
"@grafana/e2e-selectors": "npm:12.2.0-pre"
"@grafana/google-sdk": "npm:0.3.4"
"@grafana/plugin-configs": "npm:12.2.0-pre"
"@grafana/plugin-ui": "npm:0.10.7"
"@grafana/plugin-ui": "npm:0.10.8"
"@grafana/runtime": "npm:12.2.0-pre"
"@grafana/schema": "npm:12.2.0-pre"
"@grafana/ui": "npm:12.2.0-pre"
@ -2936,7 +2936,7 @@ __metadata:
"@grafana/monaco-logql": "npm:^0.0.8"
"@grafana/o11y-ds-frontend": "workspace:*"
"@grafana/plugin-configs": "npm:12.2.0-pre"
"@grafana/plugin-ui": "npm:0.10.7"
"@grafana/plugin-ui": "npm:0.10.8"
"@grafana/runtime": "workspace:*"
"@grafana/schema": "workspace:*"
"@grafana/ui": "workspace:*"
@ -2994,7 +2994,7 @@ __metadata:
"@grafana/e2e-selectors": "workspace:*"
"@grafana/o11y-ds-frontend": "workspace:*"
"@grafana/plugin-configs": "workspace:*"
"@grafana/plugin-ui": "npm:0.10.7"
"@grafana/plugin-ui": "npm:0.10.8"
"@grafana/runtime": "workspace:*"
"@grafana/ui": "workspace:*"
"@testing-library/dom": "npm:10.4.1"
@ -3379,7 +3379,7 @@ __metadata:
"@emotion/css": "npm:11.13.5"
"@grafana/data": "npm:12.2.0-pre"
"@grafana/e2e-selectors": "npm:12.2.0-pre"
"@grafana/plugin-ui": "npm:0.10.7"
"@grafana/plugin-ui": "npm:0.10.8"
"@grafana/runtime": "npm:12.2.0-pre"
"@grafana/schema": "npm:12.2.0-pre"
"@grafana/tsconfig": "npm:^2.0.0"
@ -3450,9 +3450,9 @@ __metadata:
languageName: node
linkType: hard
"@grafana/plugin-ui@npm:0.10.7, @grafana/plugin-ui@npm:^0.10.1":
version: 0.10.7
resolution: "@grafana/plugin-ui@npm:0.10.7"
"@grafana/plugin-ui@npm:0.10.8, @grafana/plugin-ui@npm:^0.10.1":
version: 0.10.8
resolution: "@grafana/plugin-ui@npm:0.10.8"
dependencies:
"@emotion/css": "npm:^11.11.2"
"@hello-pangea/dnd": "npm:^17.0.0"
@ -3474,7 +3474,7 @@ __metadata:
react: ^18.2.0
react-dom: ^18.2.0
rxjs: ^7.8.1
checksum: 10/4fa878d042ef138cfb9fdcee7de883dca8e1e222f23f2ae852e98b18e0b095f8e741547d1c89c38c48f3274149a4d61515b4fc9ccfcaa77fa62221cb5b6ee0cf
checksum: 10/3ef5eddd9a517fe2c7a0796f3d96dcd802153b625c46d7b2733f91f0378c6a8423022cae238b3bb6f554845f7e1bf63447ed8f9d3a6a5ada7d3df1e53c9cbf09
languageName: node
linkType: hard
@ -3487,7 +3487,7 @@ __metadata:
"@grafana/data": "npm:12.2.0-pre"
"@grafana/e2e-selectors": "npm:12.2.0-pre"
"@grafana/i18n": "npm:12.2.0-pre"
"@grafana/plugin-ui": "npm:0.10.7"
"@grafana/plugin-ui": "npm:0.10.8"
"@grafana/runtime": "npm:12.2.0-pre"
"@grafana/schema": "npm:12.2.0-pre"
"@grafana/tsconfig": "npm:^2.0.0"
@ -3663,7 +3663,7 @@ __metadata:
"@grafana/data": "npm:12.2.0-pre"
"@grafana/e2e-selectors": "npm:12.2.0-pre"
"@grafana/i18n": "npm:12.2.0-pre"
"@grafana/plugin-ui": "npm:0.10.7"
"@grafana/plugin-ui": "npm:0.10.8"
"@grafana/runtime": "npm:12.2.0-pre"
"@grafana/tsconfig": "npm:^2.0.0"
"@grafana/ui": "npm:12.2.0-pre"
@ -18219,7 +18219,7 @@ __metadata:
"@grafana/monaco-logql": "npm:^0.0.8"
"@grafana/o11y-ds-frontend": "workspace:*"
"@grafana/plugin-e2e": "npm:2.1.7"
"@grafana/plugin-ui": "npm:0.10.7"
"@grafana/plugin-ui": "npm:0.10.8"
"@grafana/prometheus": "workspace:*"
"@grafana/runtime": "workspace:*"
"@grafana/scenes": "npm:6.29.1"