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> { ...@@ -19,6 +19,7 @@ export interface IconProps extends React.HTMLAttributes<HTMLDivElement> {
const getIconStyles = stylesFactory((theme: GrafanaTheme) => { const getIconStyles = stylesFactory((theme: GrafanaTheme) => {
return { return {
container: css` container: css`
label: Icon;
display: inline-block; display: inline-block;
`, `,
icon: css` icon: css`
......
...@@ -119,6 +119,7 @@ const getStyles = stylesFactory( ...@@ -119,6 +119,7 @@ const getStyles = stylesFactory(
return { return {
layout: css` layout: css`
label: HorizontalGroup;
display: flex; display: flex;
flex-direction: ${orientation === Orientation.Vertical ? 'column' : 'row'}; flex-direction: ${orientation === Orientation.Vertical ? 'column' : 'row'};
flex-wrap: ${wrap ? 'wrap' : 'nowrap'}; flex-wrap: ${wrap ? 'wrap' : 'nowrap'};
......
...@@ -68,10 +68,10 @@ export const QueryOperationRow: React.FC<QueryOperationRowProps> = ({ ...@@ -68,10 +68,10 @@ export const QueryOperationRow: React.FC<QueryOperationRowProps> = ({
<div className={styles.header}> <div className={styles.header}>
<div className={styles.titleWrapper} onClick={onRowToggle} aria-label="Query operation row title"> <div className={styles.titleWrapper} onClick={onRowToggle} aria-label="Query operation row title">
<Icon name={isContentVisible ? 'angle-down' : 'angle-right'} className={styles.collapseIcon} /> <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} {headerElement}
</div> </div>
{actions && actionsElement} {actions && <div>{actionsElement}</div>}
{draggable && ( {draggable && (
<Icon title="Drag and drop to reorder" name="draggabledots" size="lg" className={styles.dragIcon} /> <Icon title="Drag and drop to reorder" name="draggabledots" size="lg" className={styles.dragIcon} />
)} )}
...@@ -113,7 +113,6 @@ const getQueryOperationRowStyles = stylesFactory((theme: GrafanaTheme) => { ...@@ -113,7 +113,6 @@ const getQueryOperationRowStyles = stylesFactory((theme: GrafanaTheme) => {
border-radius: ${theme.border.radius.sm}; border-radius: ${theme.border.radius.sm};
background: ${theme.colors.bg2}; background: ${theme.colors.bg2};
min-height: ${theme.spacing.formInputHeight}px; min-height: ${theme.spacing.formInputHeight}px;
line-height: ${theme.spacing.sm}px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
...@@ -143,6 +142,7 @@ const getQueryOperationRowStyles = stylesFactory((theme: GrafanaTheme) => { ...@@ -143,6 +142,7 @@ const getQueryOperationRowStyles = stylesFactory((theme: GrafanaTheme) => {
font-weight: ${theme.typography.weight.semibold}; font-weight: ${theme.typography.weight.semibold};
color: ${theme.colors.textBlue}; color: ${theme.colors.textBlue};
margin-left: ${theme.spacing.sm}; margin-left: ${theme.spacing.sm};
overflow: hidden;
`, `,
content: css` content: css`
margin-top: ${theme.spacing.inlineFormMargin}; margin-top: ${theme.spacing.inlineFormMargin};
......
import React from 'react'; import React from 'react';
// @ts-ignore import { render, screen } from '@testing-library/react';
import renderer from 'react-test-renderer';
import { TeamPicker } from './TeamPicker'; import { TeamPicker } from './TeamPicker';
jest.mock('@grafana/runtime', () => ({ jest.mock('@grafana/runtime', () => ({
...@@ -18,7 +17,7 @@ describe('TeamPicker', () => { ...@@ -18,7 +17,7 @@ describe('TeamPicker', () => {
const props = { const props = {
onSelected: () => {}, onSelected: () => {},
}; };
const tree = renderer.create(<TeamPicker {...props} />).toJSON(); render(<TeamPicker {...props} />);
expect(tree).toMatchSnapshot(); expect(screen.getByTestId('teamPicker')).toBeInTheDocument();
}); });
}); });
...@@ -64,7 +64,7 @@ export class TeamPicker extends Component<Props, State> { ...@@ -64,7 +64,7 @@ export class TeamPicker extends Component<Props, State> {
const { onSelected, className } = this.props; const { onSelected, className } = this.props;
const { isLoading } = this.state; const { isLoading } = this.state;
return ( return (
<div className="user-picker"> <div className="user-picker" data-testid="teamPicker">
<AsyncSelect <AsyncSelect
isLoading={isLoading} isLoading={isLoading}
defaultOptions={true} defaultOptions={true}
......
import React from 'react'; import React from 'react';
// @ts-ignore import { render, screen } from '@testing-library/react';
import renderer from 'react-test-renderer';
import { UserPicker } from './UserPicker'; import { UserPicker } from './UserPicker';
jest.mock('@grafana/runtime', () => ({ jest.mock('@grafana/runtime', () => ({
...@@ -9,7 +8,7 @@ jest.mock('@grafana/runtime', () => ({ ...@@ -9,7 +8,7 @@ jest.mock('@grafana/runtime', () => ({
describe('UserPicker', () => { describe('UserPicker', () => {
it('renders correctly', () => { it('renders correctly', () => {
const tree = renderer.create(<UserPicker onSelected={() => {}} />).toJSON(); render(<UserPicker onSelected={() => {}} />);
expect(tree).toMatchSnapshot(); expect(screen.getByTestId('userPicker')).toBeInTheDocument();
}); });
}); });
...@@ -64,7 +64,7 @@ export class UserPicker extends Component<Props, State> { ...@@ -64,7 +64,7 @@ export class UserPicker extends Component<Props, State> {
const { isLoading } = this.state; const { isLoading } = this.state;
return ( return (
<div className="user-picker"> <div className="user-picker" data-testid="userPicker">
<AsyncSelect <AsyncSelect
className={className} className={className}
isLoading={isLoading} 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 React from 'react';
import { css } from 'emotion'; import { css } from 'emotion';
import { DataQuery, DataSourceApi, GrafanaTheme } from '@grafana/data'; 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'; import { selectors } from '@grafana/e2e-selectors';
interface QueryEditorRowTitleProps { interface QueryEditorRowTitleProps {
...@@ -25,7 +25,7 @@ export const QueryEditorRowTitle: React.FC<QueryEditorRowTitleProps> = ({ ...@@ -25,7 +25,7 @@ export const QueryEditorRowTitle: React.FC<QueryEditorRowTitleProps> = ({
const styles = getQueryEditorRowTitleStyles(theme); const styles = getQueryEditorRowTitleStyles(theme);
return ( return (
<HorizontalGroup align="center"> <div className={styles.wrapper}>
<div className={styles.refId} aria-label={selectors.components.QueryEditorRow.title(query.refId)}> <div className={styles.refId} aria-label={selectors.components.QueryEditorRow.title(query.refId)}>
<span>{query.refId}</span> <span>{query.refId}</span>
{inMixedMode && <em className={styles.contextInfo}> ({datasource.name})</em>} {inMixedMode && <em className={styles.contextInfo}> ({datasource.name})</em>}
...@@ -36,12 +36,17 @@ export const QueryEditorRowTitle: React.FC<QueryEditorRowTitleProps> = ({ ...@@ -36,12 +36,17 @@ export const QueryEditorRowTitle: React.FC<QueryEditorRowTitleProps> = ({
{collapsedText} {collapsedText}
</div> </div>
)} )}
</HorizontalGroup> </div>
); );
}; };
const getQueryEditorRowTitleStyles = stylesFactory((theme: GrafanaTheme) => { const getQueryEditorRowTitleStyles = stylesFactory((theme: GrafanaTheme) => {
return { return {
wrapper: css`
display: flex;
align-items: center;
`,
refId: css` refId: css`
font-weight: ${theme.typography.weight.semibold}; font-weight: ${theme.typography.weight.semibold};
color: ${theme.colors.textBlue}; color: ${theme.colors.textBlue};
...@@ -53,10 +58,8 @@ const getQueryEditorRowTitleStyles = stylesFactory((theme: GrafanaTheme) => { ...@@ -53,10 +58,8 @@ const getQueryEditorRowTitleStyles = stylesFactory((theme: GrafanaTheme) => {
font-weight: ${theme.typography.weight.regular}; font-weight: ${theme.typography.weight.regular};
font-size: ${theme.typography.size.sm}; font-size: ${theme.typography.size.sm};
color: ${theme.colors.textWeak}; color: ${theme.colors.textWeak};
padding: 0 10px; padding-left: ${theme.spacing.sm};
display: flex;
align-items: center; align-items: center;
flex-grow: 1;
overflow: hidden; overflow: hidden;
font-style: italic; font-style: italic;
overflow: hidden; overflow: hidden;
......
import React from 'react'; import React from 'react';
import renderer from 'react-test-renderer'; import { render, screen } from '@testing-library/react';
import { shallow } from 'enzyme'; import { shallow } from 'enzyme';
import { Segment } from '@grafana/ui'; import { Segment } from '@grafana/ui';
import { Aggregations, Props } from './Aggregations'; import { Aggregations, Props } from './Aggregations';
...@@ -22,8 +22,8 @@ const props: Props = { ...@@ -22,8 +22,8 @@ const props: Props = {
describe('Aggregations', () => { describe('Aggregations', () => {
it('renders correctly', () => { it('renders correctly', () => {
const tree = renderer.create(<Aggregations {...props} />).toJSON(); render(<Aggregations {...props} />);
expect(tree).toMatchSnapshot(); expect(screen.getByTestId('aggregations')).toBeInTheDocument();
}); });
describe('options', () => { describe('options', () => {
......
...@@ -22,7 +22,7 @@ export const Aggregations: FC<Props> = props => { ...@@ -22,7 +22,7 @@ export const Aggregations: FC<Props> = props => {
const selected = useSelectedFromOptions(aggOptions, props); const selected = useSelectedFromOptions(aggOptions, props);
return ( return (
<> <div data-testid="aggregations">
<div className="gf-form-inline"> <div className="gf-form-inline">
<label className="gf-form-label query-keyword width-9">Aggregation</label> <label className="gf-form-label query-keyword width-9">Aggregation</label>
<Segment <Segment
...@@ -40,7 +40,7 @@ export const Aggregations: FC<Props> = props => { ...@@ -40,7 +40,7 @@ export const Aggregations: FC<Props> = props => {
}, },
]} ]}
placeholder="Select Reducer" placeholder="Select Reducer"
></Segment> />
<div className="gf-form gf-form--grow"> <div className="gf-form gf-form--grow">
<label className="gf-form-label gf-form-label--grow"> <label className="gf-form-label gf-form-label--grow">
<a onClick={() => setDisplayAdvancedOptions(!displayAdvancedOptions)}> <a onClick={() => setDisplayAdvancedOptions(!displayAdvancedOptions)}>
...@@ -52,7 +52,7 @@ export const Aggregations: FC<Props> = props => { ...@@ -52,7 +52,7 @@ export const Aggregations: FC<Props> = props => {
</div> </div>
</div> </div>
{props.children(displayAdvancedOptions)} {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 React from 'react';
import renderer from 'react-test-renderer'; import { render, screen } from '@testing-library/react';
import { Stats } from './Stats'; import { Stats } from './Stats';
const toOption = (value: any) => ({ label: value, value }); const toOption = (value: any) => ({ label: value, value });
describe('Stats', () => { describe('Stats', () => {
it('should render component', () => { it('should render component', () => {
const tree = renderer render(
.create( <Stats
<Stats values={['Average', 'Minimum']}
values={['Average', 'Minimum']} variableOptionGroup={{ label: 'templateVar', value: 'templateVar' }}
variableOptionGroup={{ label: 'templateVar', value: 'templateVar' }} onChange={() => {}}
onChange={() => {}} stats={['Average', 'Maximum', 'Minimum', 'Sum', 'SampleCount'].map(toOption)}
stats={['Average', 'Maximum', 'Minimum', 'Sum', 'SampleCount'].map(toOption)} />
/> );
) expect(screen.getByTestId('stats')).toBeInTheDocument();
.toJSON();
expect(tree).toMatchSnapshot();
}); });
}); });
...@@ -14,7 +14,7 @@ const removeText = '-- remove stat --'; ...@@ -14,7 +14,7 @@ const removeText = '-- remove stat --';
const removeOption: SelectableValue<string> = { label: removeText, value: removeText }; const removeOption: SelectableValue<string> = { label: removeText, value: removeText };
export const Stats: FunctionComponent<Props> = ({ stats, values, onChange, variableOptionGroup }) => ( export const Stats: FunctionComponent<Props> = ({ stats, values, onChange, variableOptionGroup }) => (
<> <div data-testid="stats">
{values && {values &&
values.map((value, index) => ( values.map((value, index) => (
<Segment <Segment
...@@ -41,5 +41,5 @@ export const Stats: FunctionComponent<Props> = ({ stats, values, onChange, varia ...@@ -41,5 +41,5 @@ export const Stats: FunctionComponent<Props> = ({ stats, values, onChange, varia
onChange={({ value }) => onChange([...values, value!])} onChange={({ value }) => onChange([...values, value!])}
options={[...stats.filter(({ value }) => !values.includes(value!)), variableOptionGroup]} 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