Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add visual editor for working with data sources #211

Merged
merged 14 commits into from
Jan 31, 2024
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
Loading