From 6dbb803b3f1dbf1e8276d84eeda05efb8083f1f8 Mon Sep 17 00:00:00 2001 From: Dominik Prokop Date: Mon, 31 Aug 2020 08:47:27 +0200 Subject: [PATCH] Transformations: enable transformations reordering (#27197) * Transformations: enable queries reorder by drag and drop * Satisfy ts * Update unicons and replace ellipsis with draggabledot * remove import * Remove that snap * Review * review 2 --- .../src/selectors/components.ts | 7 +- packages/grafana-ui/package.json | 2 +- .../components/ValuePicker/ValuePicker.tsx | 27 +-- packages/grafana-ui/src/types/icon.ts | 4 +- .../QueryOperationRow.test.tsx | 18 +- .../QueryOperationRow/QueryOperationRow.tsx | 63 +++++-- .../OrganizeFieldsTransformerEditor.tsx | 8 +- .../components/Inspector/InspectDataTab.tsx | 4 +- .../TransformationEditor.tsx | 10 +- .../TransformationOperationRow.tsx | 10 +- .../TransformationsEditor.test.tsx | 89 +++++++++ .../TransformationsEditor.tsx | 171 +++++++++++++----- .../dashboard/panel_editor/QueryEditorRow.tsx | 16 +- .../panel_editor/QueryEditorRows.tsx | 2 + .../dashboard/panel_editor/QueryOptions.tsx | 2 + .../features/explore/TimeSyncButton.test.tsx | 4 - .../TimeSyncButton.test.tsx.snap | 55 ------ yarn.lock | 7 + 18 files changed, 338 insertions(+), 161 deletions(-) create mode 100644 public/app/features/dashboard/components/TransformationsEditor/TransformationsEditor.test.tsx delete mode 100644 public/app/features/explore/__snapshots__/TimeSyncButton.test.tsx.snap diff --git a/packages/grafana-e2e-selectors/src/selectors/components.ts b/packages/grafana-e2e-selectors/src/selectors/components.ts index 10887aa5858..4f00b458d39 100644 --- a/packages/grafana-e2e-selectors/src/selectors/components.ts +++ b/packages/grafana-e2e-selectors/src/selectors/components.ts @@ -103,7 +103,9 @@ export const Components = { }, TransformTab: { content: 'Transform editor tab content', - newTransform: (title: string) => `New transform ${title}`, + newTransform: (name: string) => `New transform ${name}`, + transformationEditor: (name: string) => `Transformation editor ${name}`, + transformationEditorDebugger: (name: string) => `Transformation editor debugger ${name}`, }, Transforms: { Reduce: { @@ -144,4 +146,7 @@ export const Components = { container: 'Time zone picker select container', }, QueryField: { container: 'Query field' }, + ValuePicker: { + select: (name: string) => `Value picker select ${name}`, + }, }; diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index 2db66afc021..b97619cda7e 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -31,7 +31,7 @@ "@grafana/e2e-selectors": "7.2.0-pre.0", "@grafana/slate-react": "0.22.9-grafana", "@grafana/tsconfig": "^1.0.0-rc1", - "@iconscout/react-unicons": "^1.0.0", + "@iconscout/react-unicons": "1.1.4", "@torkelo/react-select": "3.0.8", "@types/react-beautiful-dnd": "12.1.2", "@types/react-color": "3.0.1", diff --git a/packages/grafana-ui/src/components/ValuePicker/ValuePicker.tsx b/packages/grafana-ui/src/components/ValuePicker/ValuePicker.tsx index fcc29638e02..cae6b5d608e 100644 --- a/packages/grafana-ui/src/components/ValuePicker/ValuePicker.tsx +++ b/packages/grafana-ui/src/components/ValuePicker/ValuePicker.tsx @@ -5,6 +5,7 @@ import { Button, ButtonVariant } from '../Button'; import { Select } from '../Select/Select'; import { FullWidthButtonContainer } from '../Button/FullWidthButtonContainer'; import { ComponentSize } from '../../types/size'; +import { selectors } from '@grafana/e2e-selectors'; interface ValuePickerProps { /** Label to display on the picker button */ @@ -42,18 +43,20 @@ export function ValuePicker({ {!isPicking && (isFullWidth ? {buttonEl} : buttonEl)} {isPicking && ( - setIsPicking(false)} + autoFocus={true} + onChange={value => { + setIsPicking(false); + onChange(value); + }} + menuPlacement={menuPlacement} + /> + )} ); diff --git a/packages/grafana-ui/src/types/icon.ts b/packages/grafana-ui/src/types/icon.ts index d319687565b..d5c22a7afac 100644 --- a/packages/grafana-ui/src/types/icon.ts +++ b/packages/grafana-ui/src/types/icon.ts @@ -114,7 +114,8 @@ export type IconName = | 'favorite' | 'line-alt' | 'sort-amount-down' - | 'cloud'; + | 'cloud' + | 'draggabledots'; export const getAvailableIcons = (): IconName[] => [ 'fa fa-spinner', @@ -228,4 +229,5 @@ export const getAvailableIcons = (): IconName[] => [ 'favorite', 'sort-amount-down', 'cloud', + 'draggabledots', ]; diff --git a/public/app/core/components/QueryOperationRow/QueryOperationRow.test.tsx b/public/app/core/components/QueryOperationRow/QueryOperationRow.test.tsx index 818e78051f5..e16f110d2bb 100644 --- a/public/app/core/components/QueryOperationRow/QueryOperationRow.test.tsx +++ b/public/app/core/components/QueryOperationRow/QueryOperationRow.test.tsx @@ -7,7 +7,7 @@ describe('QueryOperationRow', () => { it('renders', () => { expect(() => shallow( - +
Test
) @@ -20,7 +20,7 @@ describe('QueryOperationRow', () => { // @ts-ignore strict null error, you shouldn't use promise like approach with act but I don't know what the intention is here await act(async () => { shallow( - +
Test
); @@ -32,7 +32,7 @@ describe('QueryOperationRow', () => { const onOpenSpy = jest.fn(); const onCloseSpy = jest.fn(); const wrapper = mount( - +
Test
); @@ -60,7 +60,7 @@ describe('QueryOperationRow', () => { it('should render title provided as element', () => { const title =
Test
; const wrapper = shallow( - +
Test
); @@ -71,7 +71,7 @@ describe('QueryOperationRow', () => { it('should render title provided as function', () => { const title = () =>
Test
; const wrapper = shallow( - +
Test
); @@ -87,7 +87,7 @@ describe('QueryOperationRow', () => { return
Test
; }; shallow( - +
Test
); @@ -100,7 +100,7 @@ describe('QueryOperationRow', () => { it('should render actions provided as element', () => { const actions =
Test
; const wrapper = shallow( - +
Test
); @@ -111,7 +111,7 @@ describe('QueryOperationRow', () => { it('should render actions provided as function', () => { const actions = () =>
Test
; const wrapper = shallow( - +
Test
); @@ -127,7 +127,7 @@ describe('QueryOperationRow', () => { return
Test
; }; shallow( - +
Test
); diff --git a/public/app/core/components/QueryOperationRow/QueryOperationRow.tsx b/public/app/core/components/QueryOperationRow/QueryOperationRow.tsx index d7a3128a1b7..f5545adb85b 100644 --- a/public/app/core/components/QueryOperationRow/QueryOperationRow.tsx +++ b/public/app/core/components/QueryOperationRow/QueryOperationRow.tsx @@ -1,10 +1,13 @@ -import React, { useState } from 'react'; +import React, { useState, useCallback } from 'react'; import { HorizontalGroup, Icon, renderOrCallToRender, stylesFactory, useTheme } from '@grafana/ui'; import { GrafanaTheme } from '@grafana/data'; import { css } from 'emotion'; import { useUpdateEffect } from 'react-use'; +import { Draggable } from 'react-beautiful-dnd'; interface QueryOperationRowProps { + index: number; + id: string; title?: ((props: { isOpen: boolean }) => React.ReactNode) | React.ReactNode; headerElement?: React.ReactNode; actions?: @@ -14,6 +17,7 @@ interface QueryOperationRowProps { onClose?: () => void; children: React.ReactNode; isOpen?: boolean; + draggable?: boolean; } export const QueryOperationRow: React.FC = ({ @@ -24,10 +28,16 @@ export const QueryOperationRow: React.FC = ({ onClose, onOpen, isOpen, + draggable, + index, + id, }: QueryOperationRowProps) => { const [isContentVisible, setIsContentVisible] = useState(isOpen !== undefined ? isOpen : true); const theme = useTheme(); const styles = getQueryOperationRowStyles(theme); + const onRowToggle = useCallback(() => { + setIsContentVisible(!isContentVisible); + }, [isContentVisible, setIsContentVisible]); useUpdateEffect(() => { if (isContentVisible) { @@ -54,24 +64,37 @@ export const QueryOperationRow: React.FC = ({ }, }); - return ( + const rowHeader = ( +
+ +
+ {draggable && ( + + )} + + {title && {titleElement}} + {headerElement} +
+ {actions && actionsElement} +
+
+ ); + return draggable ? ( + + {provided => { + return ( + <> +
+
{rowHeader}
+ {isContentVisible &&
{children}
} +
+ + ); + }} +
+ ) : (
-
- -
{ - setIsContentVisible(!isContentVisible); - }} - aria-label="Query operation row title" - > - - {title && {titleElement}} - {headerElement} -
- {actions && actionsElement} -
-
+ {rowHeader} {isContentVisible &&
{children}
}
); @@ -92,6 +115,10 @@ const getQueryOperationRowStyles = stylesFactory((theme: GrafanaTheme) => { align-items: center; justify-content: space-between; `, + dragIcon: css` + opacity: 0.4; + cursor: drag; + `, collapseIcon: css` color: ${theme.colors.textWeak}; &:hover { diff --git a/public/app/core/components/TransformersUI/OrganizeFieldsTransformerEditor.tsx b/public/app/core/components/TransformersUI/OrganizeFieldsTransformerEditor.tsx index 2748c184013..5aa5649fe46 100644 --- a/public/app/core/components/TransformersUI/OrganizeFieldsTransformerEditor.tsx +++ b/public/app/core/components/TransformersUI/OrganizeFieldsTransformerEditor.tsx @@ -1,5 +1,5 @@ import React, { useCallback, useMemo } from 'react'; -import { css, cx } from 'emotion'; +import { css } from 'emotion'; import { DragDropContext, Draggable, Droppable, DropResult } from 'react-beautiful-dnd'; import { DataFrame, @@ -10,7 +10,7 @@ import { TransformerUIProps, getFieldDisplayName, } from '@grafana/data'; -import { stylesFactory, useTheme, Input, IconButton } from '@grafana/ui'; +import { stylesFactory, useTheme, Input, IconButton, Icon } from '@grafana/ui'; import { OrganizeFieldsTransformerOptions } from '@grafana/data/src/transformations/transformers/organize'; import { createOrderFieldsComparer } from '@grafana/data/src/transformations/transformers/order'; @@ -135,7 +135,7 @@ const DraggableFieldName: React.FC = ({ >
- + ({ color: ${theme.colors.textWeak}; `, draggable: css` - padding: 0 ${theme.spacing.xs}; - font-size: ${theme.typography.size.md}; opacity: 0.4; &:hover { color: ${theme.colors.textStrong}; diff --git a/public/app/features/dashboard/components/Inspector/InspectDataTab.tsx b/public/app/features/dashboard/components/Inspector/InspectDataTab.tsx index 51f3aed37a6..d210c30b3ec 100644 --- a/public/app/features/dashboard/components/Inspector/InspectDataTab.tsx +++ b/public/app/features/dashboard/components/Inspector/InspectDataTab.tsx @@ -64,7 +64,7 @@ export class InspectDataTab extends PureComponent { const { timeIndex, timeField } = getTimeField(dataFrame); if (timeField) { - // Use the configurd date or standandard time display + // Use the configured date or standard time display let processor: DisplayProcessor | undefined = timeField.display; if (!processor) { processor = getDisplayProcessor({ @@ -224,6 +224,8 @@ export class InspectDataTab extends PureComponent { return ( {this.getActiveString()}} isOpen={false} diff --git a/public/app/features/dashboard/components/TransformationsEditor/TransformationEditor.tsx b/public/app/features/dashboard/components/TransformationsEditor/TransformationEditor.tsx index 272a78d4067..929b8c21a39 100644 --- a/public/app/features/dashboard/components/TransformationsEditor/TransformationEditor.tsx +++ b/public/app/features/dashboard/components/TransformationsEditor/TransformationEditor.tsx @@ -2,6 +2,7 @@ import React, { useContext } from 'react'; import { css } from 'emotion'; import { Icon, JSONFormatter, ThemeContext } from '@grafana/ui'; import { GrafanaTheme, DataFrame } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; interface TransformationEditorProps { name: string; @@ -12,15 +13,18 @@ interface TransformationEditorProps { debugMode?: boolean; } -export const TransformationEditor = ({ editor, input, output, debugMode }: TransformationEditorProps) => { +export const TransformationEditor = ({ editor, input, output, debugMode, name }: TransformationEditorProps) => { const theme = useContext(ThemeContext); const styles = getStyles(theme); return ( -
+
{editor} {debugMode && ( -
+
Transformation input data
diff --git a/public/app/features/dashboard/components/TransformationsEditor/TransformationOperationRow.tsx b/public/app/features/dashboard/components/TransformationsEditor/TransformationOperationRow.tsx index a3d47fbd4ea..e3763f56076 100644 --- a/public/app/features/dashboard/components/TransformationsEditor/TransformationOperationRow.tsx +++ b/public/app/features/dashboard/components/TransformationsEditor/TransformationOperationRow.tsx @@ -6,17 +6,21 @@ import { QueryOperationRow } from 'app/core/components/QueryOperationRow/QueryOp import { QueryOperationAction } from 'app/core/components/QueryOperationRow/QueryOperationAction'; interface TransformationOperationRowProps { + id: string; + index: number; name: string; description?: string; - editor?: JSX.Element; - onRemove: () => void; input: DataFrame[]; output: DataFrame[]; + editor?: JSX.Element; + onRemove: () => void; } export const TransformationOperationRow: React.FC = ({ children, onRemove, + index, + id, ...props }) => { const [showDebug, setShowDebug] = useState(false); @@ -39,7 +43,7 @@ export const TransformationOperationRow: React.FC + ); diff --git a/public/app/features/dashboard/components/TransformationsEditor/TransformationsEditor.test.tsx b/public/app/features/dashboard/components/TransformationsEditor/TransformationsEditor.test.tsx new file mode 100644 index 00000000000..4615f104f74 --- /dev/null +++ b/public/app/features/dashboard/components/TransformationsEditor/TransformationsEditor.test.tsx @@ -0,0 +1,89 @@ +import React from 'react'; +import { DataTransformerConfig, standardTransformersRegistry } from '@grafana/data'; +import { render, screen, fireEvent } from '@testing-library/react'; +import { TransformationsEditor } from './TransformationsEditor'; +import { PanelModel } from '../../state'; +import { getStandardTransformers } from 'app/core/utils/standardTransformers'; +import { selectors } from '@grafana/e2e-selectors'; + +const setup = (transformations: DataTransformerConfig[] = []) => { + const panel = new PanelModel({}); + panel.setTransformations(transformations); + render(); +}; + +describe('TransformationsEditor', () => { + standardTransformersRegistry.setInit(getStandardTransformers); + + describe('when no transformations configured', () => { + it('renders transformations selection list', () => { + setup(); + + const cards = screen.getAllByLabelText(/^New transform/i); + expect(cards.length).toEqual(standardTransformersRegistry.list().length); + }); + }); + + describe('when transformations configured', () => { + it('renders transformation editors', () => { + setup([ + { + id: 'reduce', + options: {}, + }, + ]); + const editors = screen.getAllByLabelText(/^Transformation editor/g); + expect(editors).toHaveLength(1); + }); + }); + + describe('when Add transformation clicked', () => { + it('renders transformations picker', () => { + const buttonLabel = 'Add transformation'; + setup([ + { + id: 'reduce', + options: {}, + }, + ]); + + const addTransformationButton = screen.getByText(buttonLabel); + fireEvent( + addTransformationButton, + new MouseEvent('click', { + bubbles: true, + cancelable: true, + }) + ); + + const picker = screen.getByLabelText(selectors.components.ValuePicker.select(buttonLabel)); + expect(picker).toBeDefined(); + }); + }); + + describe('actions', () => { + describe('debug', () => { + it('should show/hide debugger', () => { + setup([ + { + id: 'reduce', + options: {}, + }, + ]); + const debuggerSelector = selectors.components.TransformTab.transformationEditorDebugger('Reduce'); + + expect(screen.queryByLabelText(debuggerSelector)).toBeNull(); + + const debugButton = screen.getByLabelText(selectors.components.QueryEditorRow.actionButton('Debug')); + fireEvent( + debugButton, + new MouseEvent('click', { + bubbles: true, + cancelable: true, + }) + ); + expect(screen.getByLabelText(debuggerSelector)).toBeInTheDocument(); + }); + }); + }); +}); diff --git a/public/app/features/dashboard/components/TransformationsEditor/TransformationsEditor.tsx b/public/app/features/dashboard/components/TransformationsEditor/TransformationsEditor.tsx index 60e7934fe5f..dc02c12633d 100644 --- a/public/app/features/dashboard/components/TransformationsEditor/TransformationsEditor.tsx +++ b/public/app/features/dashboard/components/TransformationsEditor/TransformationsEditor.tsx @@ -26,28 +26,55 @@ import { selectors } from '@grafana/e2e-selectors'; import { Unsubscribable } from 'rxjs'; import { PanelModel } from '../../state'; import { getDocsLink } from 'app/core/utils/docsLinks'; +import { DragDropContext, Droppable, DropResult } from 'react-beautiful-dnd'; -interface Props { +interface TransformationsEditorProps { panel: PanelModel; } +interface TransformationsEditorTransformation { + transformation: DataTransformerConfig; + id: string; +} + interface State { data: DataFrame[]; - transformations: DataTransformerConfig[]; + transformations: TransformationsEditorTransformation[]; } -export class TransformationsEditor extends React.PureComponent { +export class TransformationsEditor extends React.PureComponent { subscription?: Unsubscribable; - constructor(props: Props) { + constructor(props: TransformationsEditorProps) { super(props); + const transformations = props.panel.transformations || []; + const ids = this.buildTransformationIds(transformations); this.state = { - transformations: props.panel.transformations || [], + transformations: transformations.map((t, i) => ({ + transformation: t, + id: ids[i], + })), data: [], }; } + buildTransformationIds(transformations: DataTransformerConfig[]) { + const transformationCounters: Record = {}; + const transformationIds: string[] = []; + + for (let i = 0; i < transformations.length; i++) { + const transformation = transformations[i]; + if (transformationCounters[transformation.id] === undefined) { + transformationCounters[transformation.id] = 0; + } else { + transformationCounters[transformation.id] += 1; + } + transformationIds.push(`${transformations[i].id}-${transformationCounters[transformations[i].id]}`); + } + return transformationIds; + } + componentDidMount() { this.subscription = this.props.panel .getQueryRunner() @@ -63,19 +90,37 @@ export class TransformationsEditor extends React.PureComponent { } } - onChange(transformations: DataTransformerConfig[]) { - this.props.panel.setTransformations(transformations); + onChange(transformations: TransformationsEditorTransformation[]) { this.setState({ transformations }); + this.props.panel.setTransformations(transformations.map(t => t.transformation)); } + // Transformation uid are stored in a name-X form. name is NOT unique hence we need to parse the ids and increase X + // for transformations with the same name + getTransformationNextId = (name: string) => { + const { transformations } = this.state; + let nextId = 0; + const existingIds = transformations.filter(t => t.id.startsWith(name)).map(t => t.id); + + if (existingIds.length !== 0) { + nextId = Math.max(...existingIds.map(i => parseInt(i.match(/\d+/)![0], 10))) + 1; + } + + return `${name}-${nextId}`; + }; + onTransformationAdd = (selectable: SelectableValue) => { const { transformations } = this.state; + const nextId = this.getTransformationNextId(selectable.value!); this.onChange([ ...transformations, { - id: selectable.value as string, - options: {}, + id: nextId, + transformation: { + id: selectable.value as string, + options: {}, + }, }, ]); }; @@ -83,7 +128,7 @@ export class TransformationsEditor extends React.PureComponent { onTransformationChange = (idx: number, config: DataTransformerConfig) => { const { transformations } = this.state; const next = Array.from(transformations); - next[idx] = config; + next[idx].transformation = config; this.onChange(next); }; @@ -122,48 +167,86 @@ export class TransformationsEditor extends React.PureComponent { ); }; + onDragEnd = (result: DropResult) => { + const { transformations } = this.state; + + if (!result || !result.destination) { + return; + } + + const startIndex = result.source.index; + const endIndex = result.destination.index; + if (startIndex === endIndex) { + return; + } + const update = Array.from(transformations); + const [removed] = update.splice(startIndex, 1); + update.splice(endIndex, 0, removed); + this.onChange(update); + }; + renderTransformationEditors = () => { const { data, transformations } = this.state; return ( - <> - {transformations.map((t, i) => { - let editor; + + + {provided => { + return ( +
+ {transformations.map((t, i) => { + // Transformations are not identified uniquely by any property apart from array index. + // For drag and drop to work we need to generate unique ids. This record stores counters for each transformation type + // based on which ids are generated + let editor; - const transformationUI = standardTransformersRegistry.getIfExists(t.id); - if (!transformationUI) { - return null; - } + const transformationUI = standardTransformersRegistry.getIfExists(t.transformation.id); + if (!transformationUI) { + return null; + } - const input = transformDataFrame(transformations.slice(0, i), data); - const output = transformDataFrame(transformations.slice(i), input); + const input = transformDataFrame( + transformations.slice(0, i).map(t => t.transformation), + data + ); + const output = transformDataFrame( + transformations.slice(i).map(t => t.transformation), + input + ); - if (transformationUI) { - editor = React.createElement(transformationUI.editor, { - options: { ...transformationUI.transformation.defaultOptions, ...t.options }, - input, - onChange: (options: any) => { - this.onTransformationChange(i, { - id: t.id, - options, - }); - }, - }); - } + if (transformationUI) { + editor = React.createElement(transformationUI.editor, { + options: { ...transformationUI.transformation.defaultOptions, ...t.transformation.options }, + input, + onChange: (options: any) => { + this.onTransformationChange(i, { + id: t.transformation.id, + options, + }); + }, + }); + } - return ( - this.onTransformationRemove(i)} - editor={editor} - name={transformationUI ? transformationUI.name : ''} - description={transformationUI ? transformationUI.description : ''} - /> - ); - })} - + return ( + this.onTransformationRemove(i)} + editor={editor} + name={transformationUI.name} + description={transformationUI.description} + /> + ); + })} + {provided.placeholder} +
+ ); + }} +
+
); }; diff --git a/public/app/features/dashboard/panel_editor/QueryEditorRow.tsx b/public/app/features/dashboard/panel_editor/QueryEditorRow.tsx index 563f103df94..c2e3be4ae47 100644 --- a/public/app/features/dashboard/panel_editor/QueryEditorRow.tsx +++ b/public/app/features/dashboard/panel_editor/QueryEditorRow.tsx @@ -31,12 +31,14 @@ interface Props { data: PanelData; query: DataQuery; dashboard: DashboardModel; + dataSourceValue: string | null; + inMixedMode?: boolean; + id: string; + index: number; onAddQuery: (query?: DataQuery) => void; onRemoveQuery: (query: DataQuery) => void; onMoveQuery: (query: DataQuery, direction: number) => void; onChange: (query: DataQuery) => void; - dataSourceValue: string | null; - inMixedMode?: boolean; } interface State { @@ -276,7 +278,7 @@ export class QueryEditorRow extends PureComponent { }; render() { - const { query } = this.props; + const { query, id, index } = this.props; const { datasource } = this.state; const isDisabled = query.hide; @@ -293,7 +295,13 @@ export class QueryEditorRow extends PureComponent { return (
- +
{editor}
diff --git a/public/app/features/dashboard/panel_editor/QueryEditorRows.tsx b/public/app/features/dashboard/panel_editor/QueryEditorRows.tsx index 7364a140606..5d5dedc3912 100644 --- a/public/app/features/dashboard/panel_editor/QueryEditorRows.tsx +++ b/public/app/features/dashboard/panel_editor/QueryEditorRows.tsx @@ -81,6 +81,8 @@ export class QueryEditorRows extends PureComponent { return props.queries.map((query, index) => ( { return ( { }; describe('TimeSyncButton', () => { - it('should render component', () => { - const wrapper = setup(true); - expect(wrapper).toMatchSnapshot(); - }); it('should change style when synced', () => { const wrapper = setup(true); expect(wrapper.find('button').props()['aria-label']).toEqual('Synced times'); diff --git a/public/app/features/explore/__snapshots__/TimeSyncButton.test.tsx.snap b/public/app/features/explore/__snapshots__/TimeSyncButton.test.tsx.snap deleted file mode 100644 index 522606a1231..00000000000 --- a/public/app/features/explore/__snapshots__/TimeSyncButton.test.tsx.snap +++ /dev/null @@ -1,55 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`TimeSyncButton should render component 1`] = ` - - - - - - - -`; diff --git a/yarn.lock b/yarn.lock index 116403bafbc..aee5bd68528 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3430,6 +3430,13 @@ resolved "https://registry.yarnpkg.com/@icons/material/-/material-0.2.4.tgz#e90c9f71768b3736e76d7dd6783fc6c2afa88bc8" integrity sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw== +"@iconscout/react-unicons@1.1.4": + version "1.1.4" + resolved "https://registry.yarnpkg.com/@iconscout/react-unicons/-/react-unicons-1.1.4.tgz#30731707e1baa49ab1c3198a5e0121be86b8928a" + integrity sha512-lhTSU3nKvzt1OwsRfFyK194QcQbE1Q4rRm6d5BOnKyZB+SN4qRv7tS4wLQgwk/pQyzn14Qw70nGA+tuKLRqcJg== + dependencies: + react ">=15.0.0 <17.0.0" + "@iconscout/react-unicons@^1.0.0": version "1.0.1" resolved "https://registry.yarnpkg.com/@iconscout/react-unicons/-/react-unicons-1.0.1.tgz#b5309fac3b0cc27014da1e48edf29dd3d54a672f"