Commit 16b04160 by Andrej Ocenas Committed by GitHub

Explore: Move data source loader into the select (#19465)

parent 9a68236d
......@@ -28,7 +28,7 @@ const handleThemeChange = (theme: string) => {
}
};
// automatically import all files ending in *.stories.tsx
const req = require.context('../src/components', true, /.story.tsx$/);
const req = require.context('../src', true, /.story.tsx$/);
addDecorator(withKnobs);
addDecorator(withPaddedStory);
......
......@@ -11,7 +11,8 @@ import { default as ReactAsyncSelect } from '@torkelo/react-select/lib/Async';
import { components } from '@torkelo/react-select';
// Components
import { SelectOption, SingleValue } from './SelectOption';
import { SelectOption } from './SelectOption';
import { SingleValue } from './SingleValue';
import SelectOptionGroup from './SelectOptionGroup';
import IndicatorsContainer from './IndicatorsContainer';
import NoOptionsMessage from './NoOptionsMessage';
......
......@@ -30,18 +30,4 @@ export const SelectOption = (props: ExtendedOptionProps) => {
);
};
// was not able to type this without typescript error
export const SingleValue = (props: any) => {
const { children, data } = props;
return (
<components.SingleValue {...props}>
<div className="gf-form-select-box__img-value">
{data.imgUrl && <img className="gf-form-select-box__desc-option__img" src={data.imgUrl} />}
{children}
</div>
</components.SingleValue>
);
};
export default SelectOption;
import React from 'react';
import { css, cx } from 'emotion';
// Ignoring because I couldn't get @types/react-select work wih Torkel's fork
// @ts-ignore
import { components } from '@torkelo/react-select';
import { FadeTransition, Spinner } from '..';
import { useDelayedSwitch } from '../../utils/useDelayedSwitch';
import { stylesFactory } from '../../themes';
const getStyles = stylesFactory(() => {
const container = css`
width: 16px;
height: 16px;
display: inline-block;
margin-right: 10px;
position: relative;
vertical-align: middle;
`;
const item = css`
width: 100%;
height: 100%;
position: absolute;
`;
return { container, item };
});
type Props = {
children: React.ReactNode;
data: {
imgUrl?: string;
loading?: boolean;
};
};
export const SingleValue = (props: Props) => {
const { children, data } = props;
const styles = getStyles();
const loading = useDelayedSwitch(data.loading || false, { delay: 250, duration: 750 });
return (
<components.SingleValue {...props}>
<div className={cx('gf-form-select-box__img-value')}>
<div className={styles.container}>
<FadeTransition duration={150} visible={loading}>
<Spinner className={styles.item} inline />
</FadeTransition>
{data.imgUrl && (
<FadeTransition duration={150} visible={!loading}>
<img className={styles.item} src={data.imgUrl} />
</FadeTransition>
)}
</div>
{children}
</div>
</components.SingleValue>
);
};
import React from 'react';
import { storiesOf } from '@storybook/react';
import { withCenteredStory } from '../../utils/storybook/withCenteredStory';
import { Spinner } from './Spinner';
const story = storiesOf('UI/Spinner', module);
story.addDecorator(withCenteredStory);
story.add('spinner', () => {
return (
<div>
<Spinner />
</div>
);
});
import React, { FC } from 'react';
import { cx, css } from 'emotion';
import { stylesFactory } from '../../themes';
const getStyles = stylesFactory((size: number, inline: boolean) => {
return {
wrapper: css`
font-size: ${size}px;
${inline
? css`
display: inline-block;
`
: ''}
`,
};
});
type Props = {
className?: string;
style?: React.CSSProperties;
iconClassName?: string;
inline?: boolean;
size?: number;
};
export const Spinner: FC<Props> = (props: Props) => {
const { className, inline = false, iconClassName, style, size = 16 } = props;
const styles = getStyles(size, inline);
return (
<div style={style} className={cx(styles.wrapper, className)}>
<i className={cx('fa fa-spinner fa-spin', iconClassName)} />
</div>
);
};
......@@ -86,3 +86,5 @@ export { JSONFormatter } from './JSONFormatter/JSONFormatter';
export { JsonExplorer } from './JSONFormatter/json_explorer/json_explorer';
export { ErrorBoundary, ErrorBoundaryAlert } from './ErrorBoundary/ErrorBoundary';
export { AlphaNotice } from './AlphaNotice/AlphaNotice';
export { Spinner } from './Spinner/Spinner';
export { FadeTransition } from './transitions/FadeTransition';
import React from 'react';
import { css } from 'emotion';
import { CSSTransition } from 'react-transition-group';
import { stylesFactory } from '../../themes';
const getStyles = stylesFactory((duration: number) => {
return {
enter: css`
label: enter;
opacity: 0;
`,
enterActive: css`
label: enterActive;
opacity: 1;
transition: opacity ${duration}ms ease-out;
`,
exit: css`
label: exit;
opacity: 1;
`,
exitActive: css`
label: exitActive;
opacity: 0;
transition: opacity ${duration}ms ease-out;
`,
};
});
type Props = {
children: React.ReactNode;
visible: boolean;
duration?: number;
};
export function FadeTransition(props: Props) {
const { visible, children, duration = 250 } = props;
const styles = getStyles(duration);
return (
<CSSTransition in={visible} mountOnEnter={true} unmountOnExit={true} timeout={duration} classNames={styles}>
{children}
</CSSTransition>
);
}
import React from 'react';
import { storiesOf } from '@storybook/react';
import { withCenteredStory } from './storybook/withCenteredStory';
import { useDelayedSwitch } from './useDelayedSwitch';
import { boolean, number } from '@storybook/addon-knobs';
const getKnobs = () => {
return {
value: boolean('Value', false),
duration: number('Duration to stay on', 2000),
delay: number('Delay before switching on', 2000),
};
};
function StoryWrapper() {
const { value, delay = 0, duration = 0 } = getKnobs();
const valueDelayed = useDelayedSwitch(value, { delay, duration });
return <div>{valueDelayed ? 'ON' : 'OFF'}</div>;
}
const story = storiesOf('Utils/useDelayedSwitch', module);
story.addDecorator(withCenteredStory);
story.add('useDelayedSwitch', () => {
return <StoryWrapper />;
});
import { useEffect, useRef, useState } from 'react';
type DelayOptions = {
// Minimal amount of time the switch will be on.
duration?: number;
// Delay after which switch will turn on.
delay?: number;
};
/**
* Hook that delays changing of boolean switch to prevent too much time spent in "on" state. It is kind of a throttle
* but you can specify different time for on and off throttling so this only allows a boolean values and also prefers
* to stay "off" so turning "on" is always delayed while turning "off" is throttled.
*
* This is useful for showing loading elements to prevent it flashing too much in case of quick loading time or
* prevent it flash if loaded state comes right after switch to loading.
*/
export function useDelayedSwitch(value: boolean, options: DelayOptions = {}): boolean {
const { duration = 250, delay = 250 } = options;
const [delayedValue, setDelayedValue] = useState(value);
const onStartTime = useRef<Date | undefined>();
useEffect(() => {
let timeout: number | undefined;
if (value) {
// If toggling to "on" state we always setTimout no matter how long we have been "off".
timeout = setTimeout(() => {
onStartTime.current = new Date();
setDelayedValue(value);
}, delay) as any;
} else {
// If toggling to "off" state we check how much time we were already "on".
const timeSpent = onStartTime.current ? Date.now() - onStartTime.current.valueOf() : 0;
const turnOff = () => {
onStartTime.current = undefined;
setDelayedValue(value);
};
if (timeSpent >= duration) {
// We already spent enough time "on" so change right away.
turnOff();
} else {
timeout = setTimeout(turnOff, duration - timeSpent) as any;
}
}
return () => {
if (timeout) {
clearTimeout(timeout);
timeout = undefined;
}
};
}, [value, duration, delay]);
return delayedValue;
}
......@@ -15,6 +15,7 @@ export interface Props {
onBlur?: () => void;
autoFocus?: boolean;
openMenuOnFocus?: boolean;
showLoading?: boolean;
}
export class DataSourcePicker extends PureComponent<Props> {
......@@ -35,7 +36,7 @@ export class DataSourcePicker extends PureComponent<Props> {
};
render() {
const { datasources, current, autoFocus, onBlur, openMenuOnFocus } = this.props;
const { datasources, current, autoFocus, onBlur, openMenuOnFocus, showLoading } = this.props;
const options = datasources.map(ds => ({
value: ds.name,
......@@ -47,6 +48,7 @@ export class DataSourcePicker extends PureComponent<Props> {
label: current.name,
value: current.name,
imgUrl: current.meta.info.logos.small,
loading: showLoading,
};
return (
......
......@@ -67,7 +67,6 @@ interface ExploreProps {
changeSize: typeof changeSize;
datasourceError: string;
datasourceInstance: DataSourceApi;
datasourceLoading: boolean | null;
datasourceMissing: boolean;
exploreId: ExploreId;
initializeExplore: typeof initializeExplore;
......@@ -251,7 +250,6 @@ export class Explore extends React.PureComponent<ExploreProps> {
StartPage,
datasourceInstance,
datasourceError,
datasourceLoading,
datasourceMissing,
exploreId,
showingStartPage,
......@@ -272,7 +270,6 @@ export class Explore extends React.PureComponent<ExploreProps> {
return (
<div className={exploreClass} ref={this.getRef}>
<ExploreToolbar exploreId={exploreId} onChangeTime={this.onChangeTime} />
{datasourceLoading ? <div className="explore-container">Loading datasource...</div> : null}
{datasourceMissing ? this.renderEmptyState() : null}
<FadeIn duration={datasourceError ? 150 : 5} in={datasourceError ? true : false}>
......@@ -360,7 +357,6 @@ function mapStateToProps(state: StoreState, { exploreId }: ExploreProps): Partia
StartPage,
datasourceError,
datasourceInstance,
datasourceLoading,
datasourceMissing,
initialized,
showingStartPage,
......@@ -406,7 +402,6 @@ function mapStateToProps(state: StoreState, { exploreId }: ExploreProps): Partia
StartPage,
datasourceError,
datasourceInstance,
datasourceLoading,
datasourceMissing,
initialized,
showingStartPage,
......
......@@ -68,6 +68,7 @@ interface StateProps {
isPaused: boolean;
originPanelId: number;
queries: DataQuery[];
datasourceLoading: boolean | null;
}
interface DispatchProps {
......@@ -156,6 +157,7 @@ export class UnConnectedExploreToolbar extends PureComponent<Props, {}> {
isLive,
isPaused,
originPanelId,
datasourceLoading,
} = this.props;
const styles = getStyles();
......@@ -193,6 +195,7 @@ export class UnConnectedExploreToolbar extends PureComponent<Props, {}> {
onChange={this.onChangeDatasource}
datasources={exploreDatasources}
current={selectedDatasource}
showLoading={datasourceLoading}
/>
</div>
{supportedModes.length > 1 ? (
......@@ -316,6 +319,7 @@ const mapStateToProps = (state: StoreState, { exploreId }: OwnProps): StateProps
isPaused,
originPanelId,
queries,
datasourceLoading,
} = exploreItem;
const selectedDatasource = datasourceInstance
? exploreDatasources.find(datasource => datasource.name === datasourceInstance.name)
......@@ -339,6 +343,7 @@ const mapStateToProps = (state: StoreState, { exploreId }: OwnProps): StateProps
isPaused,
originPanelId,
queries,
datasourceLoading,
};
};
......
......@@ -318,6 +318,9 @@ Array [
<div
className="gf-form-select-box__img-value"
>
<div
className="css-zyq2zu"
/>
stackdriver auto
</div>
</div>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment