Skip to content

Commit

Permalink
Add visual editor for working with data sources (#211)
Browse files Browse the repository at this point in the history
* Clear chart when data is loaded

* Add Visual Editor support

* Revert changes

* Add Dataset Editor

* Fix typings

* Add Series Editor

* Add visual editor code

* * Update visual editor code execution
* Add DatasetEditor tests

* Add tests for VisualEditor

* Add tests for SeriesEditor

* Fix visual editor tests

* Add context object to code

* Update version

---------

Co-authored-by: Mikhail <[email protected]>
Co-authored-by: Mikhail Volkov <[email protected]>
  • Loading branch information
3 people authored Jan 31, 2024
1 parent 76cc42f commit 82eaf0a
Show file tree
Hide file tree
Showing 41 changed files with 2,629 additions and 50 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### Features / Enhancements

- Updated README and documentation (#214)
- Add visual editor for working with data sources (#211)

## 5.1.0 (2023-08-11)

Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@
identification within third-party archives.

Copyright 2020 Bilibala
Copyright 2022-2023 Volkov Labs
Copyright 2022-2024 Volkov Labs

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down
35 changes: 32 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@
"echarts-stat": "^1.2.0",
"echarts-wordcloud": "^2.1.0",
"react": "18.2.0",
"react-beautiful-dnd": "^13.1.1",
"react-dom": "18.2.0",
"tslib": "^2.6.1"
"tslib": "^2.6.1",
"uuid": "^9.0.0"
},
"description": "Apache ECharts panel for Grafana",
"devDependencies": {
Expand All @@ -33,8 +35,11 @@
"@types/jest": "^29.5.3",
"@types/lodash": "^4.14.196",
"@types/node": "^18.17.4",
"@types/react-beautiful-dnd": "^13.1.4",
"@types/uuid": "^9.0.3",
"@types/webpack-env": "^1.18.1",
"@typescript-eslint/eslint-plugin": "^5.62.0",
"@volkovlabs/jest-selectors": "^1.2.0",
"copy-webpack-plugin": "^11.0.0",
"css-loader": "^6.8.1",
"eslint-plugin-deprecation": "^1.5.0",
Expand Down
43 changes: 43 additions & 0 deletions src/__mocks__/@grafana/ui.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React from 'react';

const actual = jest.requireActual('@grafana/ui');

/**
* Mock Select component
*/
const Select = jest.fn(({ options, onChange, value, isMulti, isClearable, ...restProps }) => (
<select
onChange={(event: any) => {
if (onChange) {
if (isMulti) {
onChange(options.filter((option: any) => event.target.values.includes(option.value)));
} else {
onChange(options.find((option: any) => option.value === event.target.value));
}
}
}}
/**
* Fix jest warnings because null value.
* For Select component in @grafana/ui should be used null to reset value.
*/
value={value === null ? '' : value?.value || value}
multiple={isMulti}
{...restProps}
>
{isClearable && (
<option key="clear" value="">
Clear
</option>
)}
{options.map(({ label, value }: any) => (
<option key={value} value={value}>
{label}
</option>
))}
</select>
));

module.exports = {
...actual,
Select,
};
34 changes: 34 additions & 0 deletions src/__mocks__/react-beautiful-dnd.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React from 'react';

const actual = jest.requireActual('@grafana/ui');

/**
* Mock DragDropContext
*/
const DragDropContext = jest.fn(({ children }) => children);

/**
* Mock Droppable
*/
const Droppable = jest.fn(({ children }) => children({}));

/**
* Draggable
*/
const Draggable = jest.fn(({ children }) => (
<div data-testid="draggable">
{children(
{
draggableProps: {},
},
{}
)}
</div>
));

module.exports = {
...actual,
DragDropContext,
Droppable,
Draggable,
};
45 changes: 45 additions & 0 deletions src/components/Collapse/Collapse.styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { css } from '@emotion/css';
import { GrafanaTheme2 } from '@grafana/data';

/**
* Styles
*/
export const Styles = (theme: GrafanaTheme2) => {
return {
root: css`
border: 1px solid ${theme.colors.border.weak};
background-color: ${theme.colors.background.primary};
`,
header: css`
label: Header;
padding: ${theme.spacing(0.5, 0.5)};
min-height: ${theme.spacing(4)};
display: flex;
align-items: center;
justify-content: space-between;
white-space: nowrap;
&:focus {
outline: none;
}
`,
title: css`
font-weight: ${theme.typography.fontWeightBold};
margin-left: ${theme.spacing(0.5)};
overflow: hidden;
text-overflow: ellipsis;
`,
collapseIcon: css`
margin-left: ${theme.spacing(0.5)};
color: ${theme.colors.text.disabled};
`,
actions: css`
margin-left: auto;
display: flex;
align-items: center;
`,
content: css`
padding: ${theme.spacing(1)};
`,
};
};
43 changes: 43 additions & 0 deletions src/components/Collapse/Collapse.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React from 'react';
import { fireEvent, render, screen } from '@testing-library/react';
import { Collapse } from './Collapse';

type Props = React.ComponentProps<typeof Collapse>;

/**
* In Test Ids
*/
const InTestIds = {
header: 'data-testid header',
content: 'data-testid content',
buttonRemove: 'data-testid button-remove',
};

describe('Collapse', () => {
/**
* Get Tested Component
*/
const getComponent = (props: Partial<Props>) => {
return <Collapse headerTestId={InTestIds.header} contentTestId={InTestIds.content} {...props} />;
};

it('Should expand content', () => {
const { rerender } = render(getComponent({ isOpen: false }));

expect(screen.queryByTestId(InTestIds.content)).not.toBeInTheDocument();

rerender(getComponent({ isOpen: true }));

expect(screen.getByTestId(InTestIds.content)).toBeInTheDocument();
});

it('Actions should not affect collapse state', () => {
const onToggle = jest.fn();

render(getComponent({ onToggle, actions: <button data-testid={InTestIds.buttonRemove}>remove</button> }));

fireEvent.click(screen.getByTestId(InTestIds.buttonRemove));

expect(onToggle).not.toHaveBeenCalled();
});
});
85 changes: 85 additions & 0 deletions src/components/Collapse/Collapse.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import React from 'react';
import { IconButton, useStyles2 } from '@grafana/ui';
import { Styles } from './Collapse.styles';

/**
* Properties
*/
interface Props {
/**
* Title
*/
title?: React.ReactElement | string;

/**
* Actions
*/
actions?: React.ReactElement;

/**
* Children
*/
children?: React.ReactElement | string;

/**
* Is Open?
*/
isOpen?: boolean;

/**
* On Toggle
*/
onToggle?: () => void;

/**
* Header Test Id
*/
headerTestId?: string;

/**
* Content Test Id
*/
contentTestId?: string;
}

/**
* Collapse
*/
export const Collapse: React.FC<Props> = ({
title,
actions,
children,
isOpen = false,
onToggle,
headerTestId,
contentTestId,
}) => {
/**
* Styles
*/
const styles = useStyles2(Styles);

return (
<div className={styles.root}>
<div className={styles.header} data-testid={headerTestId} onClick={onToggle}>
<IconButton
name={isOpen ? 'angle-down' : 'angle-right'}
tooltip={isOpen ? 'Collapse' : 'Expand'}
className={styles.collapseIcon}
aria-expanded={isOpen}
/>
<div className={styles.title}>{title}</div>
{actions && (
<div className={styles.actions} onClick={(event) => event.stopPropagation()}>
{actions}
</div>
)}
</div>
{isOpen && (
<div className={styles.content} data-testid={contentTestId}>
{children}
</div>
)}
</div>
);
};
1 change: 1 addition & 0 deletions src/components/Collapse/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './Collapse';
Loading

0 comments on commit 82eaf0a

Please sign in to comment.