Commit 6a14f830 by Peter Holmberg Committed by GitHub

Fix: Show an ellipsis if Query row title is too long (#27648)

* add overflow hidden to titleWrapper

* show ellipsis and css labels for components

* readd drag handle after bad merge

* rewrite userpicker test with rtl

* update test after adding css label to icon component

* fix more tests..
parent 1e5309a7
......@@ -19,6 +19,7 @@ export interface IconProps extends React.HTMLAttributes<HTMLDivElement> {
const getIconStyles = stylesFactory((theme: GrafanaTheme) => {
return {
container: css`
label: Icon;
display: inline-block;
`,
icon: css`
......
......@@ -119,6 +119,7 @@ const getStyles = stylesFactory(
return {
layout: css`
label: HorizontalGroup;
display: flex;
flex-direction: ${orientation === Orientation.Vertical ? 'column' : 'row'};
flex-wrap: ${wrap ? 'wrap' : 'nowrap'};
......
......@@ -68,10 +68,10 @@ export const QueryOperationRow: React.FC<QueryOperationRowProps> = ({
<div className={styles.header}>
<div className={styles.titleWrapper} onClick={onRowToggle} aria-label="Query operation row title">
<Icon name={isContentVisible ? 'angle-down' : 'angle-right'} className={styles.collapseIcon} />
{title && <span className={styles.title}>{titleElement}</span>}
{title && <div className={styles.title}>{titleElement}</div>}
{headerElement}
</div>
{actions && actionsElement}
{actions && <div>{actionsElement}</div>}
{draggable && (
<Icon title="Drag and drop to reorder" name="draggabledots" size="lg" className={styles.dragIcon} />
)}
......@@ -113,7 +113,6 @@ const getQueryOperationRowStyles = stylesFactory((theme: GrafanaTheme) => {
border-radius: ${theme.border.radius.sm};
background: ${theme.colors.bg2};
min-height: ${theme.spacing.formInputHeight}px;
line-height: ${theme.spacing.sm}px;
display: flex;
align-items: center;
justify-content: space-between;
......@@ -143,6 +142,7 @@ const getQueryOperationRowStyles = stylesFactory((theme: GrafanaTheme) => {
font-weight: ${theme.typography.weight.semibold};
color: ${theme.colors.textBlue};
margin-left: ${theme.spacing.sm};
overflow: hidden;
`,
content: css`
margin-top: ${theme.spacing.inlineFormMargin};
......
import React from 'react';
// @ts-ignore
import renderer from 'react-test-renderer';
import { render, screen } from '@testing-library/react';
import { TeamPicker } from './TeamPicker';
jest.mock('@grafana/runtime', () => ({
......@@ -18,7 +17,7 @@ describe('TeamPicker', () => {
const props = {
onSelected: () => {},
};
const tree = renderer.create(<TeamPicker {...props} />).toJSON();
expect(tree).toMatchSnapshot();
render(<TeamPicker {...props} />);
expect(screen.getByTestId('teamPicker')).toBeInTheDocument();
});
});
......@@ -64,7 +64,7 @@ export class TeamPicker extends Component<Props, State> {
const { onSelected, className } = this.props;
const { isLoading } = this.state;
return (
<div className="user-picker">
<div className="user-picker" data-testid="teamPicker">
<AsyncSelect
isLoading={isLoading}
defaultOptions={true}
......
import React from 'react';
// @ts-ignore
import renderer from 'react-test-renderer';
import { render, screen } from '@testing-library/react';
import { UserPicker } from './UserPicker';
jest.mock('@grafana/runtime', () => ({
......@@ -9,7 +8,7 @@ jest.mock('@grafana/runtime', () => ({
describe('UserPicker', () => {
it('renders correctly', () => {
const tree = renderer.create(<UserPicker onSelected={() => {}} />).toJSON();
expect(tree).toMatchSnapshot();
render(<UserPicker onSelected={() => {}} />);
expect(screen.getByTestId('userPicker')).toBeInTheDocument();
});
});
......@@ -64,7 +64,7 @@ export class UserPicker extends Component<Props, State> {
const { isLoading } = this.state;
return (
<div className="user-picker">
<div className="user-picker" data-testid="userPicker">
<AsyncSelect
className={className}
isLoading={isLoading}
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`TeamPicker renders correctly 1`] = `
<div
className="user-picker"
>
<div>
<div
className="gf-form-input gf-form-input--form-dropdown css-at6rp9-SelectContainer"
onKeyDown={[Function]}
>
<div
className="gf-form-select-box__control css-ia584n-Control"
onMouseDown={[Function]}
onTouchEnd={[Function]}
>
<div
className="gf-form-select-box__value-container css-1q9zhbr-ValueContainer"
>
<div
className="gf-form-select-box__placeholder css-d8h0m4-Placeholder"
>
Select a team
</div>
<div
className="css-zz0hea-Input"
>
<div
className="gf-form-select-box__input"
style={
Object {
"display": "inline-block",
}
}
>
<input
aria-autocomplete="list"
autoCapitalize="none"
autoComplete="off"
autoCorrect="off"
disabled={false}
id="react-select-2-input"
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
spellCheck="false"
style={
Object {
"background": 0,
"border": 0,
"boxSizing": "content-box",
"color": "inherit",
"fontSize": "inherit",
"label": "input",
"opacity": 1,
"outline": 0,
"padding": 0,
"width": "1px",
}
}
tabIndex="0"
type="text"
value=""
/>
<div
style={
Object {
"height": 0,
"left": 0,
"overflow": "scroll",
"position": "absolute",
"top": 0,
"visibility": "hidden",
"whiteSpace": "pre",
}
}
>
</div>
</div>
</div>
</div>
<div
className="gf-form-select-box__indicators css-q46mcr-IndicatorsContainer"
>
<div
className="css-1cvxpvr"
>
<svg
className="css-sr6nr"
fill="currentColor"
height={16}
style={
Object {
"marginTop": "7px",
}
}
viewBox="0 0 24 24"
width={16}
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M17,9.17a1,1,0,0,0-1.41,0L12,12.71,8.46,9.17a1,1,0,0,0-1.41,0,1,1,0,0,0,0,1.42l4.24,4.24a1,1,0,0,0,1.42,0L17,10.59A1,1,0,0,0,17,9.17Z"
/>
</svg>
</div>
</div>
</div>
</div>
</div>
</div>
`;
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`UserPicker renders correctly 1`] = `
<div
className="user-picker"
>
<div>
<div
className="gf-form-input gf-form-input--form-dropdown css-at6rp9-SelectContainer"
onKeyDown={[Function]}
>
<div
className="gf-form-select-box__control css-ia584n-Control"
onMouseDown={[Function]}
onTouchEnd={[Function]}
>
<div
className="gf-form-select-box__value-container css-1q9zhbr-ValueContainer"
>
<div
className="gf-form-select-box__placeholder css-d8h0m4-Placeholder"
>
Select user
</div>
<div
className="css-zz0hea-Input"
>
<div
className="gf-form-select-box__input"
style={
Object {
"display": "inline-block",
}
}
>
<input
aria-autocomplete="list"
autoCapitalize="none"
autoComplete="off"
autoCorrect="off"
disabled={false}
id="react-select-2-input"
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
spellCheck="false"
style={
Object {
"background": 0,
"border": 0,
"boxSizing": "content-box",
"color": "inherit",
"fontSize": "inherit",
"label": "input",
"opacity": 1,
"outline": 0,
"padding": 0,
"width": "1px",
}
}
tabIndex="0"
type="text"
value=""
/>
<div
style={
Object {
"height": 0,
"left": 0,
"overflow": "scroll",
"position": "absolute",
"top": 0,
"visibility": "hidden",
"whiteSpace": "pre",
}
}
>
</div>
</div>
</div>
</div>
<div
className="gf-form-select-box__indicators css-q46mcr-IndicatorsContainer"
>
<div
className="css-1cvxpvr"
>
<svg
className="css-sr6nr"
fill="currentColor"
height={16}
style={
Object {
"marginTop": "7px",
}
}
viewBox="0 0 24 24"
width={16}
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M17,9.17a1,1,0,0,0-1.41,0L12,12.71,8.46,9.17a1,1,0,0,0-1.41,0,1,1,0,0,0,0,1.42l4.24,4.24a1,1,0,0,0,1.42,0L17,10.59A1,1,0,0,0,17,9.17Z"
/>
</svg>
</div>
</div>
</div>
</div>
</div>
</div>
`;
import React from 'react';
import { css } from 'emotion';
import { DataQuery, DataSourceApi, GrafanaTheme } from '@grafana/data';
import { HorizontalGroup, stylesFactory, useTheme } from '@grafana/ui';
import { stylesFactory, useTheme } from '@grafana/ui';
import { selectors } from '@grafana/e2e-selectors';
interface QueryEditorRowTitleProps {
......@@ -25,7 +25,7 @@ export const QueryEditorRowTitle: React.FC<QueryEditorRowTitleProps> = ({
const styles = getQueryEditorRowTitleStyles(theme);
return (
<HorizontalGroup align="center">
<div className={styles.wrapper}>
<div className={styles.refId} aria-label={selectors.components.QueryEditorRow.title(query.refId)}>
<span>{query.refId}</span>
{inMixedMode && <em className={styles.contextInfo}> ({datasource.name})</em>}
......@@ -36,12 +36,17 @@ export const QueryEditorRowTitle: React.FC<QueryEditorRowTitleProps> = ({
{collapsedText}
</div>
)}
</HorizontalGroup>
</div>
);
};
const getQueryEditorRowTitleStyles = stylesFactory((theme: GrafanaTheme) => {
return {
wrapper: css`
display: flex;
align-items: center;
`,
refId: css`
font-weight: ${theme.typography.weight.semibold};
color: ${theme.colors.textBlue};
......@@ -53,10 +58,8 @@ const getQueryEditorRowTitleStyles = stylesFactory((theme: GrafanaTheme) => {
font-weight: ${theme.typography.weight.regular};
font-size: ${theme.typography.size.sm};
color: ${theme.colors.textWeak};
padding: 0 10px;
display: flex;
padding-left: ${theme.spacing.sm};
align-items: center;
flex-grow: 1;
overflow: hidden;
font-style: italic;
overflow: hidden;
......
import React from 'react';
import renderer from 'react-test-renderer';
import { render, screen } from '@testing-library/react';
import { shallow } from 'enzyme';
import { Segment } from '@grafana/ui';
import { Aggregations, Props } from './Aggregations';
......@@ -22,8 +22,8 @@ const props: Props = {
describe('Aggregations', () => {
it('renders correctly', () => {
const tree = renderer.create(<Aggregations {...props} />).toJSON();
expect(tree).toMatchSnapshot();
render(<Aggregations {...props} />);
expect(screen.getByTestId('aggregations')).toBeInTheDocument();
});
describe('options', () => {
......
......@@ -22,7 +22,7 @@ export const Aggregations: FC<Props> = props => {
const selected = useSelectedFromOptions(aggOptions, props);
return (
<>
<div data-testid="aggregations">
<div className="gf-form-inline">
<label className="gf-form-label query-keyword width-9">Aggregation</label>
<Segment
......@@ -40,7 +40,7 @@ export const Aggregations: FC<Props> = props => {
},
]}
placeholder="Select Reducer"
></Segment>
/>
<div className="gf-form gf-form--grow">
<label className="gf-form-label gf-form-label--grow">
<a onClick={() => setDisplayAdvancedOptions(!displayAdvancedOptions)}>
......@@ -52,7 +52,7 @@ export const Aggregations: FC<Props> = props => {
</div>
</div>
{props.children(displayAdvancedOptions)}
</>
</div>
);
};
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Aggregations renders correctly 1`] = `
Array [
<div
className="gf-form-inline"
>
<label
className="gf-form-label query-keyword width-9"
>
Aggregation
</label>
<div
className="gf-form"
onClick={[Function]}
>
<a
className="gf-form-label query-part query-placeholder"
>
Select Reducer
</a>
</div>
<div
className="gf-form gf-form--grow"
>
<label
className="gf-form-label gf-form-label--grow"
>
<a
onClick={[Function]}
>
<div
className="css-1cvxpvr"
>
<svg
className="css-sr6nr"
fill="currentColor"
height={16}
viewBox="0 0 24 24"
width={16}
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M14.83,11.29,10.59,7.05a1,1,0,0,0-1.42,0,1,1,0,0,0,0,1.41L12.71,12,9.17,15.54a1,1,0,0,0,0,1.41,1,1,0,0,0,.71.29,1,1,0,0,0,.71-.29l4.24-4.24A1,1,0,0,0,14.83,11.29Z"
/>
</svg>
</div>
Advanced Options
</a>
</label>
</div>
</div>,
<div />,
]
`;
import React from 'react';
import renderer from 'react-test-renderer';
import { render, screen } from '@testing-library/react';
import { Stats } from './Stats';
const toOption = (value: any) => ({ label: value, value });
describe('Stats', () => {
it('should render component', () => {
const tree = renderer
.create(
<Stats
values={['Average', 'Minimum']}
variableOptionGroup={{ label: 'templateVar', value: 'templateVar' }}
onChange={() => {}}
stats={['Average', 'Maximum', 'Minimum', 'Sum', 'SampleCount'].map(toOption)}
/>
)
.toJSON();
expect(tree).toMatchSnapshot();
render(
<Stats
values={['Average', 'Minimum']}
variableOptionGroup={{ label: 'templateVar', value: 'templateVar' }}
onChange={() => {}}
stats={['Average', 'Maximum', 'Minimum', 'Sum', 'SampleCount'].map(toOption)}
/>
);
expect(screen.getByTestId('stats')).toBeInTheDocument();
});
});
......@@ -14,7 +14,7 @@ const removeText = '-- remove stat --';
const removeOption: SelectableValue<string> = { label: removeText, value: removeText };
export const Stats: FunctionComponent<Props> = ({ stats, values, onChange, variableOptionGroup }) => (
<>
<div data-testid="stats">
{values &&
values.map((value, index) => (
<Segment
......@@ -41,5 +41,5 @@ export const Stats: FunctionComponent<Props> = ({ stats, values, onChange, varia
onChange={({ value }) => onChange([...values, value!])}
options={[...stats.filter(({ value }) => !values.includes(value!)), variableOptionGroup]}
/>
</>
</div>
);
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Stats should render component 1`] = `
Array [
<div
className="gf-form"
onClick={[Function]}
>
<a
className="gf-form-label query-part"
>
Average
</a>
</div>,
<div
className="gf-form"
onClick={[Function]}
>
<a
className="gf-form-label query-part"
>
Minimum
</a>
</div>,
<div
className="gf-form"
onClick={[Function]}
>
<a
className="gf-form-label query-part"
>
<div
className="css-1cvxpvr"
>
<svg
className="css-sr6nr"
fill="currentColor"
height={16}
viewBox="0 0 24 24"
width={16}
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M19,11H13V5a1,1,0,0,0-2,0v6H5a1,1,0,0,0,0,2h6v6a1,1,0,0,0,2,0V13h6a1,1,0,0,0,0-2Z"
/>
</svg>
</div>
</a>
</div>,
]
`;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment