merge master

This commit is contained in:
ryan
2019-03-19 09:26:15 -07:00
110 changed files with 4316 additions and 2242 deletions

View File

@@ -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);
});
});

View File

@@ -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>
);
}
}

View File

@@ -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>
`;

View File

@@ -1 +0,0 @@
export { Input } from './Input';

View 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;
};

View File

@@ -12,6 +12,7 @@ const setup = (propOverrides?: object) => {
{
link: {},
user: {
id: 1,
isGrafanaAdmin: false,
isSignedIn: false,
orgCount: 2,

View File

@@ -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,
};

View File

@@ -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;

View File

@@ -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) }];
}
/**

View 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');
});
});

View 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;
});
});
};

View File

@@ -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];
};