mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Transforms: Adds beta notice and updates transform descriptions (#24158)
* Transforms: Adds beta notice and updates transform descriptions * Rename organize fields * Webpack - enable images import * Introduce FeatureState type * Alow Container component grow/shrink config * Enable svg import in main app * Jest + webpack for svgs * InfoBox refactor (+ added feature info box), Badge component introduced * Update packages/grafana-ui/src/components/TransformersUI/FilterByNameTransformerEditor.tsx Co-authored-by: Carl Bergquist <carl@grafana.com> * Minor fixes * Update packages/grafana-ui/src/components/TransformersUI/OrganizeFieldsTransformerEditor.tsx Co-authored-by: Carl Bergquist <carl@grafana.com> * Update packages/grafana-ui/src/components/TransformersUI/SeriesToFieldsTransformerEditor.tsx Co-authored-by: Carl Bergquist <carl@grafana.com> * fix typo * Build storybook fixed * Fix padding Co-authored-by: Dominik Prokop <dominik.prokop@grafana.com> Co-authored-by: Carl Bergquist <carl@grafana.com>
This commit is contained in:
parent
0fe9e7e242
commit
92a16d2e10
@ -14,4 +14,7 @@ module.exports = {
|
|||||||
setupFiles: ['jest-canvas-mock', './public/test/jest-shim.ts', './public/test/jest-setup.ts'],
|
setupFiles: ['jest-canvas-mock', './public/test/jest-shim.ts', './public/test/jest-setup.ts'],
|
||||||
snapshotSerializers: ['enzyme-to-json/serializer'],
|
snapshotSerializers: ['enzyme-to-json/serializer'],
|
||||||
globals: { 'ts-jest': { isolatedModules: true } },
|
globals: { 'ts-jest': { isolatedModules: true } },
|
||||||
|
moduleNameMapper: {
|
||||||
|
'\\.svg': '<rootDir>/public/test/mocks/svg.ts',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
@ -18,7 +18,7 @@ export interface ReduceTransformerOptions {
|
|||||||
export const reduceTransformer: DataTransformerInfo<ReduceTransformerOptions> = {
|
export const reduceTransformer: DataTransformerInfo<ReduceTransformerOptions> = {
|
||||||
id: DataTransformerID.reduce,
|
id: DataTransformerID.reduce,
|
||||||
name: 'Reduce',
|
name: 'Reduce',
|
||||||
description: 'Reduce all rows to a single row and concatenate all results',
|
description: 'Reduce all rows or data points to a single value using a function like max, min, mean or last',
|
||||||
defaultOptions: {
|
defaultOptions: {
|
||||||
reducers: [ReducerID.max],
|
reducers: [ReducerID.max],
|
||||||
},
|
},
|
||||||
|
@ -74,3 +74,12 @@ export class AppPlugin<T = KeyValue> extends GrafanaPlugin<AppPluginMeta<T>> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines life cycle of a feature
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
export enum FeatureState {
|
||||||
|
alpha = 'alpha',
|
||||||
|
beta = 'beta',
|
||||||
|
}
|
||||||
|
9
packages/grafana-data/src/utils/docs.ts
Normal file
9
packages/grafana-data/src/utils/docs.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
/**
|
||||||
|
* Enumeration of documentation topics
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
export enum DocsId {
|
||||||
|
Transformations,
|
||||||
|
FieldConfig,
|
||||||
|
FieldConfigOverrides,
|
||||||
|
}
|
@ -16,3 +16,4 @@ export { getFlotPairs, getFlotPairsConstant } from './flotPairs';
|
|||||||
export { locationUtil } from './location';
|
export { locationUtil } from './location';
|
||||||
export { urlUtil, UrlQueryMap, UrlQueryValue } from './url';
|
export { urlUtil, UrlQueryMap, UrlQueryValue } from './url';
|
||||||
export { DataLinkBuiltInVars } from './dataLinks';
|
export { DataLinkBuiltInVars } from './dataLinks';
|
||||||
|
export { DocsId } from './docs';
|
||||||
|
@ -11,5 +11,10 @@
|
|||||||
},
|
},
|
||||||
"exclude": ["dist", "node_modules"],
|
"exclude": ["dist", "node_modules"],
|
||||||
"extends": "@grafana/tsconfig",
|
"extends": "@grafana/tsconfig",
|
||||||
"include": ["src/**/*.ts*", "../../public/app/types/jquery/*.ts", "../../public/app/types/sanitize-url.d.ts"]
|
"include": [
|
||||||
|
"src/**/*.ts*",
|
||||||
|
"../../public/app/types/jquery/*.ts",
|
||||||
|
"../../public/app/types/sanitize-url.d.ts",
|
||||||
|
"../../public/app/types/svg.d.ts"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
@ -6,5 +6,10 @@
|
|||||||
},
|
},
|
||||||
"exclude": ["../dist", "../node_modules"],
|
"exclude": ["../dist", "../node_modules"],
|
||||||
"extends": "../tsconfig.json",
|
"extends": "../tsconfig.json",
|
||||||
"include": ["../src/**/*.ts", "../src/**/*.tsx", "../../../public/app/types/sanitize-url.d.ts"]
|
"include": [
|
||||||
|
"../src/**/*.ts",
|
||||||
|
"../src/**/*.tsx",
|
||||||
|
"../../../public/app/types/sanitize-url.d.ts",
|
||||||
|
"../../../public/app/types/svg.d.ts"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
35
packages/grafana-ui/src/components/Badge/Badge.story.tsx
Normal file
35
packages/grafana-ui/src/components/Badge/Badge.story.tsx
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { boolean, text, select } from '@storybook/addon-knobs';
|
||||||
|
import { Badge, BadgeColor } from './Badge';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'Other/Badge',
|
||||||
|
component: Badge,
|
||||||
|
decorators: [],
|
||||||
|
parameters: {
|
||||||
|
docs: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const basic = () => {
|
||||||
|
const badgeColor = select<BadgeColor>(
|
||||||
|
'Badge color',
|
||||||
|
{
|
||||||
|
Red: 'red',
|
||||||
|
Green: 'green',
|
||||||
|
Blue: 'blue',
|
||||||
|
Orange: 'orange',
|
||||||
|
},
|
||||||
|
'blue'
|
||||||
|
);
|
||||||
|
const withIcon = boolean('With icon', true);
|
||||||
|
const tooltipText = text('Tooltip text', '');
|
||||||
|
return (
|
||||||
|
<Badge
|
||||||
|
text={'Badge label'}
|
||||||
|
color={badgeColor}
|
||||||
|
icon={withIcon ? 'rocket' : undefined}
|
||||||
|
tooltip={tooltipText.trim() === '' ? undefined : tooltipText}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
90
packages/grafana-ui/src/components/Badge/Badge.tsx
Normal file
90
packages/grafana-ui/src/components/Badge/Badge.tsx
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Icon } from '../Icon/Icon';
|
||||||
|
import { useTheme } from '../../themes/ThemeContext';
|
||||||
|
import { stylesFactory } from '../../themes/stylesFactory';
|
||||||
|
import { IconName } from '../../types';
|
||||||
|
import { Tooltip } from '../Tooltip/Tooltip';
|
||||||
|
import { getColorFromHexRgbOrName, GrafanaTheme } from '@grafana/data';
|
||||||
|
import tinycolor from 'tinycolor2';
|
||||||
|
import { css } from 'emotion';
|
||||||
|
import { HorizontalGroup } from '..';
|
||||||
|
|
||||||
|
export type BadgeColor = 'blue' | 'red' | 'green' | 'orange';
|
||||||
|
|
||||||
|
export interface BadgeProps {
|
||||||
|
text: string;
|
||||||
|
color: BadgeColor;
|
||||||
|
icon?: IconName;
|
||||||
|
tooltip?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Badge = React.memo<BadgeProps>(({ icon, color, text, tooltip }) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const styles = getStyles(theme, color);
|
||||||
|
const badge = (
|
||||||
|
<div className={styles.wrapper}>
|
||||||
|
<HorizontalGroup align="center" spacing="xs">
|
||||||
|
{icon && <Icon name={icon} size="sm" />}
|
||||||
|
<span>{text}</span>
|
||||||
|
</HorizontalGroup>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
return tooltip ? (
|
||||||
|
<Tooltip content={tooltip} placement="auto">
|
||||||
|
{badge}
|
||||||
|
</Tooltip>
|
||||||
|
) : (
|
||||||
|
badge
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
Badge.displayName = 'Badge';
|
||||||
|
|
||||||
|
const getStyles = stylesFactory((theme: GrafanaTheme, color: BadgeColor) => {
|
||||||
|
let sourceColor = getColorFromHexRgbOrName(color);
|
||||||
|
let borderColor = '';
|
||||||
|
let bgColor = '';
|
||||||
|
let textColor = '';
|
||||||
|
|
||||||
|
if (theme.isDark) {
|
||||||
|
bgColor = tinycolor(sourceColor)
|
||||||
|
.darken(38)
|
||||||
|
.toString();
|
||||||
|
borderColor = tinycolor(sourceColor)
|
||||||
|
.darken(25)
|
||||||
|
.toString();
|
||||||
|
textColor = tinycolor(sourceColor)
|
||||||
|
.lighten(45)
|
||||||
|
.toString();
|
||||||
|
} else {
|
||||||
|
bgColor = tinycolor(sourceColor)
|
||||||
|
.lighten(30)
|
||||||
|
.toString();
|
||||||
|
borderColor = tinycolor(sourceColor)
|
||||||
|
.lighten(15)
|
||||||
|
.toString();
|
||||||
|
textColor = tinycolor(sourceColor)
|
||||||
|
.darken(40)
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
wrapper: css`
|
||||||
|
font-size: ${theme.typography.size.sm};
|
||||||
|
display: inline-flex;
|
||||||
|
padding: 1px 4px;
|
||||||
|
border-radius: 3px;
|
||||||
|
margin-top: 6px;
|
||||||
|
background: ${bgColor};
|
||||||
|
border: 1px solid ${borderColor};
|
||||||
|
color: ${textColor};
|
||||||
|
|
||||||
|
> span {
|
||||||
|
position: relative;
|
||||||
|
top: 1px;
|
||||||
|
margin-left: 2px;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
});
|
@ -0,0 +1,63 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { InfoBox, InfoBoxProps } from './InfoBox';
|
||||||
|
import { FeatureState, GrafanaTheme } from '@grafana/data';
|
||||||
|
import { stylesFactory, useTheme } from '../../themes';
|
||||||
|
import { Badge, BadgeProps } from '../Badge/Badge';
|
||||||
|
import { css } from 'emotion';
|
||||||
|
|
||||||
|
interface FeatureInfoBox extends Omit<InfoBoxProps, 'branded' | 'title' | 'urlTitle'> {
|
||||||
|
title: string;
|
||||||
|
featureState?: FeatureState;
|
||||||
|
}
|
||||||
|
export const FeatureInfoBox = React.memo(
|
||||||
|
React.forwardRef<HTMLDivElement, FeatureInfoBox>(({ title, featureState, ...otherProps }, ref) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const styles = getFeatureInfoBoxStyles(theme);
|
||||||
|
|
||||||
|
const titleEl = featureState ? (
|
||||||
|
<>
|
||||||
|
<div className={styles.badge}>
|
||||||
|
<FeatureBadge featureState={featureState} />
|
||||||
|
</div>
|
||||||
|
<h3>{title}</h3>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<h3>{title}</h3>
|
||||||
|
);
|
||||||
|
return <InfoBox branded title={titleEl} urlTitle="Read documentation" {...otherProps} />;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const getFeatureInfoBoxStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||||
|
return {
|
||||||
|
badge: css`
|
||||||
|
margin-bottom: ${theme.spacing.sm};
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
interface FeatureBadgeProps {
|
||||||
|
featureState: FeatureState;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FeatureBadge: React.FC<FeatureBadgeProps> = ({ featureState }) => {
|
||||||
|
const display = getPanelStateBadgeDisplayModel(featureState);
|
||||||
|
return <Badge text={display.text} color={display.color} icon={display.icon} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
function getPanelStateBadgeDisplayModel(featureState: FeatureState): BadgeProps {
|
||||||
|
switch (featureState) {
|
||||||
|
case FeatureState.alpha:
|
||||||
|
return {
|
||||||
|
text: 'Alpha',
|
||||||
|
icon: 'exclamation-triangle',
|
||||||
|
color: 'orange',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
text: 'Beta',
|
||||||
|
icon: 'rocket',
|
||||||
|
color: 'blue',
|
||||||
|
};
|
||||||
|
}
|
@ -1,6 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { number } from '@storybook/addon-knobs';
|
import { number } from '@storybook/addon-knobs';
|
||||||
import { InfoBox } from './InfoBox';
|
import { InfoBox } from './InfoBox';
|
||||||
|
import { FeatureInfoBox } from './FeatureInfoBox';
|
||||||
|
import { FeatureState } from '@grafana/data';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Layout/InfoBox',
|
title: 'Layout/InfoBox',
|
||||||
@ -35,16 +37,11 @@ export const basic = () => {
|
|||||||
return (
|
return (
|
||||||
<div style={{ width: containerWidth }}>
|
<div style={{ width: containerWidth }}>
|
||||||
<InfoBox
|
<InfoBox
|
||||||
header="User Permission"
|
title="User Permission"
|
||||||
footer={
|
url={'http://docs.grafana.org/features/datasources/mysql/'}
|
||||||
<>
|
onDismiss={() => {
|
||||||
Checkout the{' '}
|
alert('onDismiss clicked');
|
||||||
<a className="external-link" target="_blank" href="http://docs.grafana.org/features/datasources/mysql/">
|
}}
|
||||||
MySQL Data Source Docs
|
|
||||||
</a>{' '}
|
|
||||||
for more information.,
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<p>
|
<p>
|
||||||
The database user should only be granted SELECT permissions on the specified database & tables you want to
|
The database user should only be granted SELECT permissions on the specified database & tables you want to
|
||||||
@ -57,3 +54,26 @@ export const basic = () => {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const featureInfoBox = () => {
|
||||||
|
const { containerWidth } = getKnobs();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ width: containerWidth }}>
|
||||||
|
<FeatureInfoBox
|
||||||
|
title="Transformations"
|
||||||
|
url={'http://www.grafana.com'}
|
||||||
|
featureState={FeatureState.beta}
|
||||||
|
onDismiss={() => {
|
||||||
|
alert('onDismiss clicked');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Transformations allow you to join, calculate, re-order, hide and rename your query results before being
|
||||||
|
visualized. <br />
|
||||||
|
Many transforms are not suitable if your using the Graph visualisation as it currently only supports time
|
||||||
|
series. <br />
|
||||||
|
It can help to switch to Table visualisation to understand what a transformation is doing.
|
||||||
|
</FeatureInfoBox>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
@ -2,10 +2,19 @@ import React from 'react';
|
|||||||
import { css, cx } from 'emotion';
|
import { css, cx } from 'emotion';
|
||||||
import { GrafanaTheme } from '@grafana/data';
|
import { GrafanaTheme } from '@grafana/data';
|
||||||
import { stylesFactory, useTheme } from '../../themes';
|
import { stylesFactory, useTheme } from '../../themes';
|
||||||
|
import { Icon } from '../Icon/Icon';
|
||||||
|
import { IconButton } from '../IconButton/IconButton';
|
||||||
|
import { HorizontalGroup } from '../Layout/Layout';
|
||||||
|
import panelArtDark from './panelArt_dark.svg';
|
||||||
|
import panelArtLight from './panelArt_light.svg';
|
||||||
|
|
||||||
export interface Props extends React.HTMLAttributes<HTMLDivElement> {
|
export interface InfoBoxProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'title'> {
|
||||||
header?: string | JSX.Element;
|
children: React.ReactNode;
|
||||||
footer?: string | JSX.Element;
|
title?: string | JSX.Element;
|
||||||
|
url?: string;
|
||||||
|
urlTitle?: string;
|
||||||
|
branded?: boolean;
|
||||||
|
onDismiss?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -14,33 +23,39 @@ export interface Props extends React.HTMLAttributes<HTMLDivElement> {
|
|||||||
* @Alpha
|
* @Alpha
|
||||||
*/
|
*/
|
||||||
export const InfoBox = React.memo(
|
export const InfoBox = React.memo(
|
||||||
React.forwardRef<HTMLDivElement, Props>(({ header, footer, className, children, ...otherProps }, ref) => {
|
React.forwardRef<HTMLDivElement, InfoBoxProps>(
|
||||||
const theme = useTheme();
|
({ title, className, children, branded, url, urlTitle, onDismiss, ...otherProps }, ref) => {
|
||||||
const css = getInfoBoxStyles(theme);
|
const theme = useTheme();
|
||||||
|
const styles = getInfoBoxStyles(theme);
|
||||||
|
const wrapperClassName = branded ? cx(styles.wrapperBranded, className) : cx(styles.wrapper, className);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cx([css.wrapper, className])} {...otherProps} ref={ref}>
|
<div className={wrapperClassName} {...otherProps} ref={ref}>
|
||||||
{header && (
|
<div>
|
||||||
<div className={css.header}>
|
<HorizontalGroup justify={'space-between'} align={'flex-start'}>
|
||||||
<h5>{header}</h5>
|
<div>{typeof title === 'string' ? <h4>{title}</h4> : title}</div>
|
||||||
|
{onDismiss && <IconButton name={'times'} onClick={onDismiss} />}
|
||||||
|
</HorizontalGroup>
|
||||||
</div>
|
</div>
|
||||||
)}
|
<div>{children}</div>
|
||||||
{children}
|
{url && (
|
||||||
{footer && <div className={css.footer}>{footer}</div>}
|
<a href={url} className={styles.docsLink} target="_blank">
|
||||||
</div>
|
<Icon name="book" /> {urlTitle || 'Read more'}
|
||||||
);
|
</a>
|
||||||
})
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
const getInfoBoxStyles = stylesFactory((theme: GrafanaTheme) => ({
|
const getInfoBoxStyles = stylesFactory((theme: GrafanaTheme) => ({
|
||||||
wrapper: css`
|
wrapper: css`
|
||||||
position: relative;
|
position: relative;
|
||||||
padding: ${theme.spacing.lg};
|
padding: ${theme.spacing.md};
|
||||||
background-color: ${theme.colors.bg2};
|
background-color: ${theme.colors.bg2};
|
||||||
border-top: 3px solid ${theme.palette.blue80};
|
border-top: 3px solid ${theme.palette.blue80};
|
||||||
margin-bottom: ${theme.spacing.md};
|
margin-bottom: ${theme.spacing.md};
|
||||||
margin-right: ${theme.spacing.xs};
|
|
||||||
box-shadow: ${theme.shadows.listItem};
|
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
@ -60,18 +75,39 @@ const getInfoBoxStyles = stylesFactory((theme: GrafanaTheme) => ({
|
|||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
|
||||||
@extend .external-link;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--max-lg {
|
&--max-lg {
|
||||||
max-width: ${theme.breakpoints.lg};
|
max-width: ${theme.breakpoints.lg};
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
header: css`
|
wrapperBranded: css`
|
||||||
margin-bottom: ${theme.spacing.d};
|
padding: ${theme.spacing.md};
|
||||||
|
border-radius: ${theme.border.radius.md};
|
||||||
|
position: relative;
|
||||||
|
box-shadow: 0 0 30px 10px rgba(0, 0, 0, ${theme.isLight ? 0.05 : 0.2});
|
||||||
|
z-index: 0;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-image: url(${theme.isLight ? panelArtLight : panelArtDark});
|
||||||
|
border-radius: ${theme.border.radius.md};
|
||||||
|
background-position: 50% 50%;
|
||||||
|
background-size: cover;
|
||||||
|
filter: saturate(80%);
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
p:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
footer: css`
|
docsLink: css`
|
||||||
margin-top: ${theme.spacing.d};
|
display: inline-block;
|
||||||
|
margin-top: ${theme.spacing.lg};
|
||||||
|
font-size: ${theme.typography.size.sm};
|
||||||
`,
|
`,
|
||||||
}));
|
}));
|
||||||
|
58
packages/grafana-ui/src/components/InfoBox/panelArt_dark.svg
Normal file
58
packages/grafana-ui/src/components/InfoBox/panelArt_dark.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 23 KiB |
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 25 KiB |
@ -1,5 +1,5 @@
|
|||||||
import React, { HTMLProps } from 'react';
|
import React, { HTMLProps } from 'react';
|
||||||
import { css } from 'emotion';
|
import { css, cx } from 'emotion';
|
||||||
import { GrafanaTheme } from '@grafana/data';
|
import { GrafanaTheme } from '@grafana/data';
|
||||||
import { stylesFactory, useTheme } from '../../themes';
|
import { stylesFactory, useTheme } from '../../themes';
|
||||||
|
|
||||||
@ -24,6 +24,8 @@ export interface LayoutProps extends Omit<HTMLProps<HTMLDivElement>, 'align' | '
|
|||||||
export interface ContainerProps {
|
export interface ContainerProps {
|
||||||
padding?: Spacing;
|
padding?: Spacing;
|
||||||
margin?: Spacing;
|
margin?: Spacing;
|
||||||
|
grow?: number;
|
||||||
|
shrink?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Layout: React.FC<LayoutProps> = ({
|
export const Layout: React.FC<LayoutProps> = ({
|
||||||
@ -84,10 +86,26 @@ export const VerticalGroup: React.FC<Omit<LayoutProps, 'orientation' | 'wrap'>>
|
|||||||
</Layout>
|
</Layout>
|
||||||
);
|
);
|
||||||
|
|
||||||
export const Container: React.FC<ContainerProps> = ({ children, padding, margin }) => {
|
export const Container: React.FC<ContainerProps> = ({ children, padding, margin, grow, shrink }) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const styles = getContainerStyles(theme, padding, margin);
|
const styles = getContainerStyles(theme, padding, margin);
|
||||||
return <div className={styles.wrapper}>{children}</div>;
|
return (
|
||||||
|
<div
|
||||||
|
className={cx(
|
||||||
|
styles.wrapper,
|
||||||
|
grow !== undefined &&
|
||||||
|
css`
|
||||||
|
flex-grow: ${grow};
|
||||||
|
`,
|
||||||
|
shrink !== undefined &&
|
||||||
|
css`
|
||||||
|
flex-shrink: ${shrink};
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getStyles = stylesFactory(
|
const getStyles = stylesFactory(
|
||||||
|
@ -190,5 +190,5 @@ export const filterFieldsByNameTransformRegistryItem: TransformerRegistyItem<Fil
|
|||||||
editor: FilterByNameTransformerEditor,
|
editor: FilterByNameTransformerEditor,
|
||||||
transformation: standardTransformers.filterFieldsByNameTransformer,
|
transformation: standardTransformers.filterFieldsByNameTransformer,
|
||||||
name: 'Filter by name',
|
name: 'Filter by name',
|
||||||
description: 'Filter fields by name',
|
description: 'Removes part of the query results using a regex pattern. The pattern can be inclusive or exclusive.',
|
||||||
};
|
};
|
||||||
|
@ -133,6 +133,7 @@ export const filterFramesByRefIdTransformRegistryItem: TransformerRegistyItem<Fi
|
|||||||
id: DataTransformerID.filterByRefId,
|
id: DataTransformerID.filterByRefId,
|
||||||
editor: FilterByRefIdTransformerEditor,
|
editor: FilterByRefIdTransformerEditor,
|
||||||
transformation: standardTransformers.filterFramesByRefIdTransformer,
|
transformation: standardTransformers.filterFramesByRefIdTransformer,
|
||||||
name: 'Filter by refId',
|
name: 'Filter data by query',
|
||||||
description: 'Filter results by refId',
|
description:
|
||||||
|
'Filter data by query. This is useful if you are sharing the results from a different panel that has many queries and you want to only visualize a subset of that in this panel.',
|
||||||
};
|
};
|
||||||
|
@ -15,5 +15,6 @@ export const labelsToFieldsTransformerRegistryItem: TransformerRegistyItem<Label
|
|||||||
editor: LabelsAsFieldsTransformerEditor,
|
editor: LabelsAsFieldsTransformerEditor,
|
||||||
transformation: standardTransformers.labelsToFieldsTransformer,
|
transformation: standardTransformers.labelsToFieldsTransformer,
|
||||||
name: 'Labels to fields',
|
name: 'Labels to fields',
|
||||||
description: 'Groups series by time and return labels or tags as fields',
|
description: `Groups series by time and return labels or tags as fields.
|
||||||
|
Useful for showing time series with labels in a table where each label key becomes a seperate column`,
|
||||||
};
|
};
|
||||||
|
@ -221,6 +221,7 @@ export const organizeFieldsTransformRegistryItem: TransformerRegistyItem<Organiz
|
|||||||
id: DataTransformerID.organize,
|
id: DataTransformerID.organize,
|
||||||
editor: OrganizeFieldsTransformerEditor,
|
editor: OrganizeFieldsTransformerEditor,
|
||||||
transformation: standardTransformers.organizeFieldsTransformer,
|
transformation: standardTransformers.organizeFieldsTransformer,
|
||||||
name: 'Organize fields',
|
name: 'Change order, hide and rename',
|
||||||
description: 'Order, filter and rename fields',
|
description:
|
||||||
|
"Allows the user to re-order, hide, or rename columns. Useful when data source doesn't allow overrides for visualizing data.",
|
||||||
};
|
};
|
||||||
|
@ -42,6 +42,7 @@ export const seriesToFieldsTransformerRegistryItem: TransformerRegistyItem<Serie
|
|||||||
id: DataTransformerID.seriesToColumns,
|
id: DataTransformerID.seriesToColumns,
|
||||||
editor: SeriesToFieldsTransformerEditor,
|
editor: SeriesToFieldsTransformerEditor,
|
||||||
transformation: standardTransformers.seriesToColumnsTransformer,
|
transformation: standardTransformers.seriesToColumnsTransformer,
|
||||||
name: 'Join by field',
|
name: 'Outer join',
|
||||||
description: 'Joins many time series / data frames by a field',
|
description:
|
||||||
|
'Joins many time series/tables by a field. This can be used to outer join multiple time series on the _time_ field to show many time series in one table.',
|
||||||
};
|
};
|
||||||
|
@ -99,6 +99,7 @@ export { DataLinkInput } from './DataLinks/DataLinkInput';
|
|||||||
export { DataLinksContextMenu } from './DataLinks/DataLinksContextMenu';
|
export { DataLinksContextMenu } from './DataLinks/DataLinksContextMenu';
|
||||||
export { SeriesIcon } from './Legend/SeriesIcon';
|
export { SeriesIcon } from './Legend/SeriesIcon';
|
||||||
export { InfoBox } from './InfoBox/InfoBox';
|
export { InfoBox } from './InfoBox/InfoBox';
|
||||||
|
export { FeatureInfoBox } from './InfoBox/FeatureInfoBox';
|
||||||
|
|
||||||
export { JSONFormatter } from './JSONFormatter/JSONFormatter';
|
export { JSONFormatter } from './JSONFormatter/JSONFormatter';
|
||||||
export { JsonExplorer } from './JSONFormatter/json_explorer/json_explorer';
|
export { JsonExplorer } from './JSONFormatter/json_explorer/json_explorer';
|
||||||
@ -138,6 +139,7 @@ export * from './Select/Select';
|
|||||||
export { ButtonSelect } from './Select/ButtonSelect';
|
export { ButtonSelect } from './Select/ButtonSelect';
|
||||||
|
|
||||||
export { HorizontalGroup, VerticalGroup, Container } from './Layout/Layout';
|
export { HorizontalGroup, VerticalGroup, Container } from './Layout/Layout';
|
||||||
|
export { Badge, BadgeColor, BadgeProps } from './Badge/Badge';
|
||||||
export { RadioButtonGroup } from './Forms/RadioButtonGroup/RadioButtonGroup';
|
export { RadioButtonGroup } from './Forms/RadioButtonGroup/RadioButtonGroup';
|
||||||
|
|
||||||
export { Input } from './Input/Input';
|
export { Input } from './Input/Input';
|
||||||
|
@ -11,5 +11,5 @@
|
|||||||
},
|
},
|
||||||
"exclude": ["dist", "node_modules"],
|
"exclude": ["dist", "node_modules"],
|
||||||
"extends": "@grafana/tsconfig",
|
"extends": "@grafana/tsconfig",
|
||||||
"include": ["src/**/*.ts*", "../../public/app/types/sanitize-url.d.ts"]
|
"include": ["src/**/*.ts*", "../../public/app/types/sanitize-url.d.ts", "../../public/app/types/svg.d.ts"]
|
||||||
}
|
}
|
||||||
|
10
public/app/core/utils/docsLinks.ts
Normal file
10
public/app/core/utils/docsLinks.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { DocsId } from '@grafana/data';
|
||||||
|
|
||||||
|
// TODO: Documentation links
|
||||||
|
const DOCS_LINKS: Record<DocsId, string> = {
|
||||||
|
[DocsId.Transformations]: 'https://docs.grafana.com',
|
||||||
|
[DocsId.FieldConfig]: 'https://docs.grafana.com',
|
||||||
|
[DocsId.FieldConfigOverrides]: 'https://docs.grafana.com',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getDocsLink = (id: DocsId) => DOCS_LINKS[id];
|
@ -2,18 +2,20 @@ import React, { useCallback } from 'react';
|
|||||||
import cloneDeep from 'lodash/cloneDeep';
|
import cloneDeep from 'lodash/cloneDeep';
|
||||||
import {
|
import {
|
||||||
DataFrame,
|
DataFrame,
|
||||||
|
FeatureState,
|
||||||
FieldConfigPropertyItem,
|
FieldConfigPropertyItem,
|
||||||
FieldConfigSource,
|
FieldConfigSource,
|
||||||
PanelPlugin,
|
PanelPlugin,
|
||||||
SelectableValue,
|
SelectableValue,
|
||||||
VariableSuggestionsScope,
|
VariableSuggestionsScope,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
import { Container, Counter, Field, fieldMatchersUI, Label, ValuePicker } from '@grafana/ui';
|
import { Container, Counter, FeatureInfoBox, Field, fieldMatchersUI, Label, useTheme, ValuePicker } from '@grafana/ui';
|
||||||
import { getDataLinksVariableSuggestions } from '../../../panel/panellinks/link_srv';
|
import { getDataLinksVariableSuggestions } from '../../../panel/panellinks/link_srv';
|
||||||
import { OverrideEditor } from './OverrideEditor';
|
import { OverrideEditor } from './OverrideEditor';
|
||||||
import groupBy from 'lodash/groupBy';
|
import groupBy from 'lodash/groupBy';
|
||||||
import { OptionsGroup } from './OptionsGroup';
|
import { OptionsGroup } from './OptionsGroup';
|
||||||
import { selectors } from '@grafana/e2e-selectors';
|
import { selectors } from '@grafana/e2e-selectors';
|
||||||
|
import { css } from 'emotion';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
plugin: PanelPlugin;
|
plugin: PanelPlugin;
|
||||||
@ -27,6 +29,8 @@ interface Props {
|
|||||||
* Expects the container div to have size set and will fill it 100%
|
* Expects the container div to have size set and will fill it 100%
|
||||||
*/
|
*/
|
||||||
export const OverrideFieldConfigEditor: React.FC<Props> = props => {
|
export const OverrideFieldConfigEditor: React.FC<Props> = props => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const { config } = props;
|
||||||
const onOverrideChange = (index: number, override: any) => {
|
const onOverrideChange = (index: number, override: any) => {
|
||||||
const { config } = props;
|
const { config } = props;
|
||||||
let overrides = cloneDeep(config.overrides);
|
let overrides = cloneDeep(config.overrides);
|
||||||
@ -104,6 +108,19 @@ export const OverrideFieldConfigEditor: React.FC<Props> = props => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div aria-label={selectors.components.OverridesConfigEditor.content}>
|
<div aria-label={selectors.components.OverridesConfigEditor.content}>
|
||||||
|
{config.overrides.length === 0 && (
|
||||||
|
<FeatureInfoBox
|
||||||
|
title="Overrides"
|
||||||
|
featureState={FeatureState.beta}
|
||||||
|
// url={getDocsLink(DocsId.FieldConfigOverrides)}
|
||||||
|
className={css`
|
||||||
|
margin: ${theme.spacing.md};
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
Field options overrides give you a fine grained control over how your data is displayed.
|
||||||
|
</FeatureInfoBox>
|
||||||
|
)}
|
||||||
|
|
||||||
{renderOverrides()}
|
{renderOverrides()}
|
||||||
{renderAddOverride()}
|
{renderAddOverride()}
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,8 +1,18 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Button, Container, CustomScrollbar, stylesFactory, useTheme, ValuePicker, VerticalGroup } from '@grafana/ui';
|
import {
|
||||||
|
Button,
|
||||||
|
Container,
|
||||||
|
CustomScrollbar,
|
||||||
|
FeatureInfoBox,
|
||||||
|
stylesFactory,
|
||||||
|
useTheme,
|
||||||
|
ValuePicker,
|
||||||
|
VerticalGroup,
|
||||||
|
} from '@grafana/ui';
|
||||||
import {
|
import {
|
||||||
DataFrame,
|
DataFrame,
|
||||||
DataTransformerConfig,
|
DataTransformerConfig,
|
||||||
|
FeatureState,
|
||||||
GrafanaTheme,
|
GrafanaTheme,
|
||||||
SelectableValue,
|
SelectableValue,
|
||||||
standardTransformersRegistry,
|
standardTransformersRegistry,
|
||||||
@ -114,13 +124,23 @@ export class TransformationsEditor extends React.PureComponent<Props> {
|
|||||||
|
|
||||||
renderNoAddedTransformsState() {
|
renderNoAddedTransformsState() {
|
||||||
return (
|
return (
|
||||||
<>
|
<VerticalGroup spacing={'lg'}>
|
||||||
<p className="muted">
|
<Container grow={1}>
|
||||||
Transformations allow you to combine, re-order, hide and rename specific parts the the data set before being
|
<FeatureInfoBox
|
||||||
visualized. <br />
|
title="Transformations"
|
||||||
Choose one of the transformations below to start with:
|
featureState={FeatureState.beta}
|
||||||
</p>
|
// url={getDocsLink(DocsId.Transformations)}
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
Transformations allow you to join, calculate, re-order, hide and rename your query results before being
|
||||||
|
visualized. <br />
|
||||||
|
Many transforms are not suitable if your using the Graph visualisation as it currently only supports time
|
||||||
|
series. <br />
|
||||||
|
It can help to switch to Table visualisation to understand what a transformation is doing. <br />
|
||||||
|
</p>
|
||||||
|
<p>Select one of the transformations below to start.</p>
|
||||||
|
</FeatureInfoBox>
|
||||||
|
</Container>
|
||||||
<VerticalGroup>
|
<VerticalGroup>
|
||||||
{standardTransformersRegistry.list().map(t => {
|
{standardTransformersRegistry.list().map(t => {
|
||||||
return (
|
return (
|
||||||
@ -136,7 +156,7 @@ export class TransformationsEditor extends React.PureComponent<Props> {
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</VerticalGroup>
|
</VerticalGroup>
|
||||||
</>
|
</VerticalGroup>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,6 +190,11 @@ const getTransformationCardStyles = stylesFactory((theme: GrafanaTheme) => {
|
|||||||
border: none;
|
border: none;
|
||||||
padding: ${theme.spacing.sm};
|
padding: ${theme.spacing.sm};
|
||||||
|
|
||||||
|
// hack because these cards use classes from a very different card for some reason
|
||||||
|
.add-data-source-item-text {
|
||||||
|
font-size: ${theme.typography.size.md};
|
||||||
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: ${theme.colors.bg3};
|
background: ${theme.colors.bg3};
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { GrafanaTheme, PanelPluginMeta, PluginState } from '@grafana/data';
|
import { GrafanaTheme, PanelPluginMeta, PluginState } from '@grafana/data';
|
||||||
import { styleMixins, stylesFactory, useTheme } from '@grafana/ui';
|
import { Badge, BadgeProps, styleMixins, stylesFactory, useTheme } from '@grafana/ui';
|
||||||
import { css, cx } from 'emotion';
|
import { css, cx } from 'emotion';
|
||||||
import { selectors } from '@grafana/e2e-selectors';
|
import { selectors } from '@grafana/e2e-selectors';
|
||||||
import { PanelPluginBadge } from '../../plugins/PluginSignatureBadge';
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
isCurrent: boolean;
|
isCurrent: boolean;
|
||||||
@ -126,3 +125,36 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
export default VizTypePickerPlugin;
|
export default VizTypePickerPlugin;
|
||||||
|
|
||||||
|
interface PanelPluginBadgeProps {
|
||||||
|
plugin: PanelPluginMeta;
|
||||||
|
}
|
||||||
|
const PanelPluginBadge: React.FC<PanelPluginBadgeProps> = ({ plugin }) => {
|
||||||
|
const display = getPanelStateBadgeDisplayModel(plugin);
|
||||||
|
|
||||||
|
if (plugin.state !== PluginState.deprecated && plugin.state !== PluginState.alpha) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return <Badge color={display.color} text={display.text} icon={display.icon} tooltip={display.tooltip} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
function getPanelStateBadgeDisplayModel(panel: PanelPluginMeta): BadgeProps {
|
||||||
|
switch (panel.state) {
|
||||||
|
case PluginState.deprecated:
|
||||||
|
return {
|
||||||
|
text: 'Deprecated',
|
||||||
|
icon: 'exclamation-triangle',
|
||||||
|
color: 'red',
|
||||||
|
tooltip: `${panel.name} panel is deprecated`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
text: 'Alpha',
|
||||||
|
icon: 'rocket',
|
||||||
|
color: 'blue',
|
||||||
|
tooltip: `${panel.name} panel is experimental`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
PanelPluginBadge.displayName = 'PanelPluginBadge';
|
||||||
|
@ -1,61 +1,17 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Icon, IconName, stylesFactory, Tooltip, useTheme } from '@grafana/ui';
|
import { Badge, BadgeProps } from '@grafana/ui';
|
||||||
import {
|
import { PluginSignatureStatus } from '@grafana/data';
|
||||||
getColorFromHexRgbOrName,
|
|
||||||
GrafanaTheme,
|
|
||||||
PanelPluginMeta,
|
|
||||||
PluginSignatureStatus,
|
|
||||||
PluginState,
|
|
||||||
} from '@grafana/data';
|
|
||||||
import { css } from 'emotion';
|
|
||||||
import tinycolor from 'tinycolor2';
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
status: PluginSignatureStatus;
|
status: PluginSignatureStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PluginSignatureBadge: React.FC<Props> = ({ status }) => {
|
export const PluginSignatureBadge: React.FC<Props> = ({ status }) => {
|
||||||
const theme = useTheme();
|
|
||||||
const display = getSignatureDisplayModel(status);
|
const display = getSignatureDisplayModel(status);
|
||||||
const styles = getStyles(theme, display);
|
return <Badge text={display.text} color={display.color} icon={display.icon} tooltip={display.tooltip} />;
|
||||||
|
|
||||||
return (
|
|
||||||
<Tooltip content={display.tooltip} placement="left">
|
|
||||||
<div className={styles.wrapper}>
|
|
||||||
<Icon name={display.icon} size="sm" />
|
|
||||||
<span>{display.text}</span>
|
|
||||||
</div>
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
interface PanelPluginBadgeProps {
|
function getSignatureDisplayModel(signature: PluginSignatureStatus): BadgeProps {
|
||||||
plugin: PanelPluginMeta;
|
|
||||||
}
|
|
||||||
export const PanelPluginBadge: React.FC<PanelPluginBadgeProps> = ({ plugin }) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
const display = getPanelStateBadgeDisplayModel(plugin);
|
|
||||||
const styles = getStyles(theme, display);
|
|
||||||
|
|
||||||
if (plugin.state !== PluginState.deprecated && plugin.state !== PluginState.alpha) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div className={styles.wrapper}>
|
|
||||||
<Icon name={display.icon} size="sm" />
|
|
||||||
<span>{display.text}</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
interface DisplayModel {
|
|
||||||
text: string;
|
|
||||||
icon: IconName;
|
|
||||||
color: string;
|
|
||||||
tooltip: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSignatureDisplayModel(signature: PluginSignatureStatus): DisplayModel {
|
|
||||||
switch (signature) {
|
switch (signature) {
|
||||||
case PluginSignatureStatus.internal:
|
case PluginSignatureStatus.internal:
|
||||||
return { text: 'Core', icon: 'cube', color: 'blue', tooltip: 'Core plugin that is bundled with Grafana' };
|
return { text: 'Core', icon: 'cube', color: 'blue', tooltip: 'Core plugin that is bundled with Grafana' };
|
||||||
@ -80,71 +36,4 @@ function getSignatureDisplayModel(signature: PluginSignatureStatus): DisplayMode
|
|||||||
return { text: 'Unsigned', icon: 'exclamation-triangle', color: 'red', tooltip: 'Unsigned external plugin' };
|
return { text: 'Unsigned', icon: 'exclamation-triangle', color: 'red', tooltip: 'Unsigned external plugin' };
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPanelStateBadgeDisplayModel(panel: PanelPluginMeta): DisplayModel {
|
|
||||||
switch (panel.state) {
|
|
||||||
case PluginState.deprecated:
|
|
||||||
return {
|
|
||||||
text: 'Deprecated',
|
|
||||||
icon: 'exclamation-triangle',
|
|
||||||
color: 'red',
|
|
||||||
tooltip: `${panel.name} panel is deprecated`,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
text: 'Alpha',
|
|
||||||
icon: 'rocket',
|
|
||||||
color: 'blue',
|
|
||||||
tooltip: `${panel.name} panel is experimental`,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const getStyles = stylesFactory((theme: GrafanaTheme, model: DisplayModel) => {
|
|
||||||
let sourceColor = getColorFromHexRgbOrName(model.color);
|
|
||||||
let borderColor = '';
|
|
||||||
let bgColor = '';
|
|
||||||
let textColor = '';
|
|
||||||
|
|
||||||
if (theme.isDark) {
|
|
||||||
bgColor = tinycolor(sourceColor)
|
|
||||||
.darken(38)
|
|
||||||
.toString();
|
|
||||||
borderColor = tinycolor(sourceColor)
|
|
||||||
.darken(25)
|
|
||||||
.toString();
|
|
||||||
textColor = tinycolor(sourceColor)
|
|
||||||
.lighten(45)
|
|
||||||
.toString();
|
|
||||||
} else {
|
|
||||||
bgColor = tinycolor(sourceColor)
|
|
||||||
.lighten(30)
|
|
||||||
.toString();
|
|
||||||
borderColor = tinycolor(sourceColor)
|
|
||||||
.lighten(15)
|
|
||||||
.toString();
|
|
||||||
textColor = tinycolor(sourceColor)
|
|
||||||
.darken(40)
|
|
||||||
.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
wrapper: css`
|
|
||||||
font-size: ${theme.typography.size.sm};
|
|
||||||
display: inline-flex;
|
|
||||||
padding: 1px 4px;
|
|
||||||
border-radius: 3px;
|
|
||||||
margin-top: 6px;
|
|
||||||
background: ${bgColor};
|
|
||||||
border: 1px solid ${borderColor};
|
|
||||||
color: ${textColor};
|
|
||||||
|
|
||||||
> span {
|
|
||||||
position: relative;
|
|
||||||
top: 1px;
|
|
||||||
margin-left: 2px;
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
PluginSignatureBadge.displayName = 'PluginSignatureBadge';
|
PluginSignatureBadge.displayName = 'PluginSignatureBadge';
|
||||||
|
4
public/app/types/svg.d.ts
vendored
Normal file
4
public/app/types/svg.d.ts
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
declare module '*.svg' {
|
||||||
|
const content: string;
|
||||||
|
export default content;
|
||||||
|
}
|
1
public/test/mocks/svg.ts
Normal file
1
public/test/mocks/svg.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export const svg = 'svg';
|
@ -108,6 +108,11 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
test: /\.(svg|ico|jpg|jpeg|png|gif|eot|otf|webp|ttf|woff|woff2|cur|ani|pdf)(\?.*)?$/,
|
||||||
|
loader: 'file-loader',
|
||||||
|
options: { name: 'static/img/[name].[hash:8].[ext]' },
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
// https://webpack.js.org/plugins/split-chunks-plugin/#split-chunks-example-3
|
// https://webpack.js.org/plugins/split-chunks-plugin/#split-chunks-example-3
|
||||||
|
@ -86,10 +86,6 @@ module.exports = (env = {}) =>
|
|||||||
sourceMap: false,
|
sourceMap: false,
|
||||||
preserveUrl: false,
|
preserveUrl: false,
|
||||||
}),
|
}),
|
||||||
{
|
|
||||||
test: /\.(png|jpg|gif|ttf|eot|svg|woff(2)?)(\?[a-z0-9=&.]+)?$/,
|
|
||||||
loader: 'file-loader',
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user