mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
merge master
This commit is contained in:
@@ -1,53 +0,0 @@
|
||||
import React from 'react';
|
||||
import renderer from 'react-test-renderer';
|
||||
import { shallow } from 'enzyme';
|
||||
import { Input, EventsWithValidation } from './Input';
|
||||
import { ValidationEvents } from 'app/types';
|
||||
|
||||
const TEST_ERROR_MESSAGE = 'Value must be empty or less than 3 chars';
|
||||
const testBlurValidation: ValidationEvents = {
|
||||
[EventsWithValidation.onBlur]: [
|
||||
{
|
||||
rule: (value: string) => {
|
||||
if (!value || value.length < 3) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
errorMessage: TEST_ERROR_MESSAGE,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
describe('Input', () => {
|
||||
it('renders correctly', () => {
|
||||
const tree = renderer.create(<Input />).toJSON();
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should validate with error onBlur', () => {
|
||||
const wrapper = shallow(<Input validationEvents={testBlurValidation} />);
|
||||
const evt = {
|
||||
persist: jest.fn,
|
||||
target: {
|
||||
value: 'I can not be more than 2 chars',
|
||||
},
|
||||
};
|
||||
|
||||
wrapper.find('input').simulate('blur', evt);
|
||||
expect(wrapper.state('error')).toBe(TEST_ERROR_MESSAGE);
|
||||
});
|
||||
|
||||
it('should validate without error onBlur', () => {
|
||||
const wrapper = shallow(<Input validationEvents={testBlurValidation} />);
|
||||
const evt = {
|
||||
persist: jest.fn,
|
||||
target: {
|
||||
value: 'Hi',
|
||||
},
|
||||
};
|
||||
|
||||
wrapper.find('input').simulate('blur', evt);
|
||||
expect(wrapper.state('error')).toBe(null);
|
||||
});
|
||||
});
|
||||
@@ -1,94 +0,0 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { ValidationEvents, ValidationRule } from 'app/types';
|
||||
import { validate, hasValidationEvent } from 'app/core/utils/validate';
|
||||
|
||||
export enum InputStatus {
|
||||
Invalid = 'invalid',
|
||||
Valid = 'valid',
|
||||
}
|
||||
|
||||
export enum InputTypes {
|
||||
Text = 'text',
|
||||
Number = 'number',
|
||||
Password = 'password',
|
||||
Email = 'email',
|
||||
}
|
||||
|
||||
export enum EventsWithValidation {
|
||||
onBlur = 'onBlur',
|
||||
onFocus = 'onFocus',
|
||||
onChange = 'onChange',
|
||||
}
|
||||
|
||||
interface Props extends React.HTMLProps<HTMLInputElement> {
|
||||
validationEvents?: ValidationEvents;
|
||||
hideErrorMessage?: boolean;
|
||||
|
||||
// Override event props and append status as argument
|
||||
onBlur?: (event: React.FocusEvent<HTMLInputElement>, status?: InputStatus) => void;
|
||||
onFocus?: (event: React.FocusEvent<HTMLInputElement>, status?: InputStatus) => void;
|
||||
onChange?: (event: React.FormEvent<HTMLInputElement>, status?: InputStatus) => void;
|
||||
}
|
||||
|
||||
export class Input extends PureComponent<Props> {
|
||||
static defaultProps = {
|
||||
className: '',
|
||||
};
|
||||
|
||||
state = {
|
||||
error: null,
|
||||
};
|
||||
|
||||
get status() {
|
||||
return this.state.error ? InputStatus.Invalid : InputStatus.Valid;
|
||||
}
|
||||
|
||||
get isInvalid() {
|
||||
return this.status === InputStatus.Invalid;
|
||||
}
|
||||
|
||||
validatorAsync = (validationRules: ValidationRule[]) => {
|
||||
return evt => {
|
||||
const errors = validate(evt.target.value, validationRules);
|
||||
this.setState(prevState => {
|
||||
return {
|
||||
...prevState,
|
||||
error: errors ? errors[0] : null,
|
||||
};
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
populateEventPropsWithStatus = (restProps, validationEvents: ValidationEvents) => {
|
||||
const inputElementProps = { ...restProps };
|
||||
Object.keys(EventsWithValidation).forEach((eventName: EventsWithValidation) => {
|
||||
if (hasValidationEvent(eventName, validationEvents) || restProps[eventName]) {
|
||||
inputElementProps[eventName] = async evt => {
|
||||
evt.persist(); // Needed for async. https://reactjs.org/docs/events.html#event-pooling
|
||||
if (hasValidationEvent(eventName, validationEvents)) {
|
||||
await this.validatorAsync(validationEvents[eventName]).apply(this, [evt]);
|
||||
}
|
||||
if (restProps[eventName]) {
|
||||
restProps[eventName].apply(null, [evt, this.status]);
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
return inputElementProps;
|
||||
};
|
||||
|
||||
render() {
|
||||
const { validationEvents, className, hideErrorMessage, ...restProps } = this.props;
|
||||
const { error } = this.state;
|
||||
const inputClassName = classNames('gf-form-input', { invalid: this.isInvalid }, className);
|
||||
const inputElementProps = this.populateEventPropsWithStatus(restProps, validationEvents);
|
||||
|
||||
return (
|
||||
<div className="our-custom-wrapper-class">
|
||||
<input {...inputElementProps} className={inputClassName} />
|
||||
{error && !hideErrorMessage && <span>{error}</span>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Input renders correctly 1`] = `
|
||||
<div
|
||||
className="our-custom-wrapper-class"
|
||||
>
|
||||
<input
|
||||
className="gf-form-input"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
@@ -1 +0,0 @@
|
||||
export { Input } from './Input';
|
||||
13
public/app/core/components/WithFeatureToggle.tsx
Normal file
13
public/app/core/components/WithFeatureToggle.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import React, { FunctionComponent } from 'react';
|
||||
|
||||
export interface Props {
|
||||
featureToggle: boolean;
|
||||
}
|
||||
|
||||
export const WithFeatureToggle: FunctionComponent<Props> = ({ featureToggle, children }) => {
|
||||
if (featureToggle === true) {
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
@@ -12,6 +12,7 @@ const setup = (propOverrides?: object) => {
|
||||
{
|
||||
link: {},
|
||||
user: {
|
||||
id: 1,
|
||||
isGrafanaAdmin: false,
|
||||
isSignedIn: false,
|
||||
orgCount: 2,
|
||||
|
||||
@@ -13,7 +13,7 @@ export interface BuildInfo {
|
||||
|
||||
export class Settings {
|
||||
datasources: any;
|
||||
panels: PanelPlugin[];
|
||||
panels: { [key: string]: PanelPlugin };
|
||||
appSubUrl: string;
|
||||
windowTitlePrefix: string;
|
||||
buildInfo: BuildInfo;
|
||||
@@ -37,7 +37,7 @@ export class Settings {
|
||||
passwordHint: any;
|
||||
loginError: any;
|
||||
viewersCanEdit: boolean;
|
||||
editorsCanOwn: boolean;
|
||||
editorsCanAdmin: boolean;
|
||||
disableSanitizeHtml: boolean;
|
||||
theme: GrafanaTheme;
|
||||
|
||||
@@ -59,7 +59,7 @@ export class Settings {
|
||||
isEnterprise: false,
|
||||
},
|
||||
viewersCanEdit: false,
|
||||
editorsCanOwn: false,
|
||||
editorsCanAdmin: false,
|
||||
disableSanitizeHtml: false,
|
||||
};
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import _ from 'lodash';
|
||||
import coreModule from 'app/core/core_module';
|
||||
|
||||
export class User {
|
||||
id: number;
|
||||
isGrafanaAdmin: any;
|
||||
isSignedIn: any;
|
||||
orgRole: any;
|
||||
|
||||
@@ -9,6 +9,7 @@ import store from 'app/core/store';
|
||||
import { parse as parseDate } from 'app/core/utils/datemath';
|
||||
import { colors } from '@grafana/ui';
|
||||
import TableModel, { mergeTablesIntoModel } from 'app/core/table_model';
|
||||
import { getNextRefIdChar } from './query';
|
||||
|
||||
// Types
|
||||
import { RawTimeRange, IntervalValues, DataQuery, DataSourceApi } from '@grafana/ui';
|
||||
@@ -225,12 +226,8 @@ export function generateKey(index = 0): string {
|
||||
return `Q-${Date.now()}-${Math.random()}-${index}`;
|
||||
}
|
||||
|
||||
export function generateRefId(index = 0): string {
|
||||
return `${index + 1}`;
|
||||
}
|
||||
|
||||
export function generateEmptyQuery(index = 0): { refId: string; key: string } {
|
||||
return { refId: generateRefId(index), key: generateKey(index) };
|
||||
export function generateEmptyQuery(queries: DataQuery[], index = 0): DataQuery {
|
||||
return { refId: getNextRefIdChar(queries), key: generateKey(index) };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -238,9 +235,9 @@ export function generateEmptyQuery(index = 0): { refId: string; key: string } {
|
||||
*/
|
||||
export function ensureQueries(queries?: DataQuery[]): DataQuery[] {
|
||||
if (queries && typeof queries === 'object' && queries.length > 0) {
|
||||
return queries.map((query, i) => ({ ...query, ...generateEmptyQuery(i) }));
|
||||
return queries.map((query, i) => ({ ...query, ...generateEmptyQuery(queries, i) }));
|
||||
}
|
||||
return [{ ...generateEmptyQuery() }];
|
||||
return [{ ...generateEmptyQuery(queries) }];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
30
public/app/core/utils/query.test.ts
Normal file
30
public/app/core/utils/query.test.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { DataQuery } from '@grafana/ui';
|
||||
import { getNextRefIdChar } from './query';
|
||||
|
||||
const dataQueries: DataQuery[] = [
|
||||
{
|
||||
refId: 'A',
|
||||
},
|
||||
{
|
||||
refId: 'B',
|
||||
},
|
||||
{
|
||||
refId: 'C',
|
||||
},
|
||||
{
|
||||
refId: 'D',
|
||||
},
|
||||
{
|
||||
refId: 'E',
|
||||
},
|
||||
];
|
||||
|
||||
describe('Get next refId char', () => {
|
||||
it('should return next char', () => {
|
||||
expect(getNextRefIdChar(dataQueries)).toEqual('F');
|
||||
});
|
||||
|
||||
it('should get first char', () => {
|
||||
expect(getNextRefIdChar([])).toEqual('A');
|
||||
});
|
||||
});
|
||||
12
public/app/core/utils/query.ts
Normal file
12
public/app/core/utils/query.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import _ from 'lodash';
|
||||
import { DataQuery } from '@grafana/ui/';
|
||||
|
||||
export const getNextRefIdChar = (queries: DataQuery[]): string => {
|
||||
const letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
|
||||
return _.find(letters, refId => {
|
||||
return _.every(queries, other => {
|
||||
return other.refId !== refId;
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -1,16 +0,0 @@
|
||||
import { ValidationRule, ValidationEvents } from 'app/types';
|
||||
import { EventsWithValidation } from 'app/core/components/Form/Input';
|
||||
|
||||
export const validate = (value: string, validationRules: ValidationRule[]) => {
|
||||
const errors = validationRules.reduce((acc, currRule) => {
|
||||
if (!currRule.rule(value)) {
|
||||
return acc.concat(currRule.errorMessage);
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
return errors.length > 0 ? errors : null;
|
||||
};
|
||||
|
||||
export const hasValidationEvent = (event: EventsWithValidation, validationEvents: ValidationEvents) => {
|
||||
return validationEvents && validationEvents[event];
|
||||
};
|
||||
Reference in New Issue
Block a user