diff --git a/jest.config.js b/jest.config.js index 4b2c97b20ed..cda3d8a0986 100644 --- a/jest.config.js +++ b/jest.config.js @@ -14,4 +14,7 @@ module.exports = { setupFiles: ['jest-canvas-mock', './public/test/jest-shim.ts', './public/test/jest-setup.ts'], snapshotSerializers: ['enzyme-to-json/serializer'], globals: { 'ts-jest': { isolatedModules: true } }, + moduleNameMapper: { + '\\.svg': '/public/test/mocks/svg.ts', + }, }; diff --git a/packages/grafana-data/src/transformations/transformers/reduce.ts b/packages/grafana-data/src/transformations/transformers/reduce.ts index 043e3dc17ea..d4c33420813 100644 --- a/packages/grafana-data/src/transformations/transformers/reduce.ts +++ b/packages/grafana-data/src/transformations/transformers/reduce.ts @@ -18,7 +18,7 @@ export interface ReduceTransformerOptions { export const reduceTransformer: DataTransformerInfo = { id: DataTransformerID.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: { reducers: [ReducerID.max], }, diff --git a/packages/grafana-data/src/types/app.ts b/packages/grafana-data/src/types/app.ts index e0146803928..a400b53dbbe 100644 --- a/packages/grafana-data/src/types/app.ts +++ b/packages/grafana-data/src/types/app.ts @@ -74,3 +74,12 @@ export class AppPlugin extends GrafanaPlugin> { } } } + +/** + * Defines life cycle of a feature + * @internal + */ +export enum FeatureState { + alpha = 'alpha', + beta = 'beta', +} diff --git a/packages/grafana-data/src/utils/docs.ts b/packages/grafana-data/src/utils/docs.ts new file mode 100644 index 00000000000..14e2247b4db --- /dev/null +++ b/packages/grafana-data/src/utils/docs.ts @@ -0,0 +1,9 @@ +/** + * Enumeration of documentation topics + * @internal + */ +export enum DocsId { + Transformations, + FieldConfig, + FieldConfigOverrides, +} diff --git a/packages/grafana-data/src/utils/index.ts b/packages/grafana-data/src/utils/index.ts index 82f565eb882..0f3e90bf2b7 100644 --- a/packages/grafana-data/src/utils/index.ts +++ b/packages/grafana-data/src/utils/index.ts @@ -16,3 +16,4 @@ export { getFlotPairs, getFlotPairsConstant } from './flotPairs'; export { locationUtil } from './location'; export { urlUtil, UrlQueryMap, UrlQueryValue } from './url'; export { DataLinkBuiltInVars } from './dataLinks'; +export { DocsId } from './docs'; diff --git a/packages/grafana-runtime/tsconfig.json b/packages/grafana-runtime/tsconfig.json index 80ce0289af2..f8ab70ac70d 100644 --- a/packages/grafana-runtime/tsconfig.json +++ b/packages/grafana-runtime/tsconfig.json @@ -11,5 +11,10 @@ }, "exclude": ["dist", "node_modules"], "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" + ] } diff --git a/packages/grafana-ui/.storybook/tsconfig.json b/packages/grafana-ui/.storybook/tsconfig.json index 807d02f32b3..7a8f86da20c 100644 --- a/packages/grafana-ui/.storybook/tsconfig.json +++ b/packages/grafana-ui/.storybook/tsconfig.json @@ -6,5 +6,10 @@ }, "exclude": ["../dist", "../node_modules"], "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" + ] } diff --git a/packages/grafana-ui/src/components/Badge/Badge.story.tsx b/packages/grafana-ui/src/components/Badge/Badge.story.tsx new file mode 100644 index 00000000000..e76f72e5cb1 --- /dev/null +++ b/packages/grafana-ui/src/components/Badge/Badge.story.tsx @@ -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( + 'Badge color', + { + Red: 'red', + Green: 'green', + Blue: 'blue', + Orange: 'orange', + }, + 'blue' + ); + const withIcon = boolean('With icon', true); + const tooltipText = text('Tooltip text', ''); + return ( + + ); +}; diff --git a/packages/grafana-ui/src/components/Badge/Badge.tsx b/packages/grafana-ui/src/components/Badge/Badge.tsx new file mode 100644 index 00000000000..efbaab0dbfb --- /dev/null +++ b/packages/grafana-ui/src/components/Badge/Badge.tsx @@ -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(({ icon, color, text, tooltip }) => { + const theme = useTheme(); + const styles = getStyles(theme, color); + const badge = ( +
+ + {icon && } + {text} + +
+ ); + + return tooltip ? ( + + {badge} + + ) : ( + 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; + } + `, + }; +}); diff --git a/packages/grafana-ui/src/components/InfoBox/FeatureInfoBox.tsx b/packages/grafana-ui/src/components/InfoBox/FeatureInfoBox.tsx new file mode 100644 index 00000000000..43845d3e536 --- /dev/null +++ b/packages/grafana-ui/src/components/InfoBox/FeatureInfoBox.tsx @@ -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 { + title: string; + featureState?: FeatureState; +} +export const FeatureInfoBox = React.memo( + React.forwardRef(({ title, featureState, ...otherProps }, ref) => { + const theme = useTheme(); + const styles = getFeatureInfoBoxStyles(theme); + + const titleEl = featureState ? ( + <> +
+ +
+

{title}

+ + ) : ( +

{title}

+ ); + return ; + }) +); + +const getFeatureInfoBoxStyles = stylesFactory((theme: GrafanaTheme) => { + return { + badge: css` + margin-bottom: ${theme.spacing.sm}; + `, + }; +}); + +interface FeatureBadgeProps { + featureState: FeatureState; +} + +export const FeatureBadge: React.FC = ({ featureState }) => { + const display = getPanelStateBadgeDisplayModel(featureState); + return ; +}; + +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', + }; +} diff --git a/packages/grafana-ui/src/components/InfoBox/InfoBox.story.tsx b/packages/grafana-ui/src/components/InfoBox/InfoBox.story.tsx index 8579b5992ae..faf649f9cda 100644 --- a/packages/grafana-ui/src/components/InfoBox/InfoBox.story.tsx +++ b/packages/grafana-ui/src/components/InfoBox/InfoBox.story.tsx @@ -1,6 +1,8 @@ import React from 'react'; import { number } from '@storybook/addon-knobs'; import { InfoBox } from './InfoBox'; +import { FeatureInfoBox } from './FeatureInfoBox'; +import { FeatureState } from '@grafana/data'; export default { title: 'Layout/InfoBox', @@ -35,16 +37,11 @@ export const basic = () => { return (
- Checkout the{' '} - - MySQL Data Source Docs - {' '} - for more information., - - } + title="User Permission" + url={'http://docs.grafana.org/features/datasources/mysql/'} + onDismiss={() => { + alert('onDismiss clicked'); + }} >

The database user should only be granted SELECT permissions on the specified database & tables you want to @@ -57,3 +54,26 @@ export const basic = () => {

); }; + +export const featureInfoBox = () => { + const { containerWidth } = getKnobs(); + + return ( +
+ { + alert('onDismiss clicked'); + }} + > + Transformations allow you to join, calculate, re-order, hide and rename your query results before being + visualized.
+ Many transforms are not suitable if your using the Graph visualisation as it currently only supports time + series.
+ It can help to switch to Table visualisation to understand what a transformation is doing. +
+
+ ); +}; diff --git a/packages/grafana-ui/src/components/InfoBox/InfoBox.tsx b/packages/grafana-ui/src/components/InfoBox/InfoBox.tsx index 1a8ebae2499..bda80126727 100644 --- a/packages/grafana-ui/src/components/InfoBox/InfoBox.tsx +++ b/packages/grafana-ui/src/components/InfoBox/InfoBox.tsx @@ -2,10 +2,19 @@ import React from 'react'; import { css, cx } from 'emotion'; import { GrafanaTheme } from '@grafana/data'; 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 { - header?: string | JSX.Element; - footer?: string | JSX.Element; +export interface InfoBoxProps extends Omit, 'title'> { + children: React.ReactNode; + title?: string | JSX.Element; + url?: string; + urlTitle?: string; + branded?: boolean; + onDismiss?: () => void; } /** @@ -14,33 +23,39 @@ export interface Props extends React.HTMLAttributes { * @Alpha */ export const InfoBox = React.memo( - React.forwardRef(({ header, footer, className, children, ...otherProps }, ref) => { - const theme = useTheme(); - const css = getInfoBoxStyles(theme); + React.forwardRef( + ({ title, className, children, branded, url, urlTitle, onDismiss, ...otherProps }, ref) => { + const theme = useTheme(); + const styles = getInfoBoxStyles(theme); + const wrapperClassName = branded ? cx(styles.wrapperBranded, className) : cx(styles.wrapper, className); - return ( -
- {header && ( -
-
{header}
+ return ( +
+
+ +
{typeof title === 'string' ?

{title}

: title}
+ {onDismiss && } +
- )} - {children} - {footer &&
{footer}
} -
- ); - }) +
{children}
+ {url && ( + + {urlTitle || 'Read more'} + + )} +
+ ); + } + ) ); const getInfoBoxStyles = stylesFactory((theme: GrafanaTheme) => ({ wrapper: css` position: relative; - padding: ${theme.spacing.lg}; + padding: ${theme.spacing.md}; background-color: ${theme.colors.bg2}; border-top: 3px solid ${theme.palette.blue80}; margin-bottom: ${theme.spacing.md}; - margin-right: ${theme.spacing.xs}; - box-shadow: ${theme.shadows.listItem}; flex-grow: 1; ul { @@ -60,18 +75,39 @@ const getInfoBoxStyles = stylesFactory((theme: GrafanaTheme) => ({ margin-bottom: 0; } - a { - @extend .external-link; - } - &--max-lg { max-width: ${theme.breakpoints.lg}; } `, - header: css` - margin-bottom: ${theme.spacing.d}; + wrapperBranded: css` + 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` - margin-top: ${theme.spacing.d}; + docsLink: css` + display: inline-block; + margin-top: ${theme.spacing.lg}; + font-size: ${theme.typography.size.sm}; `, })); diff --git a/packages/grafana-ui/src/components/InfoBox/panelArt_dark.svg b/packages/grafana-ui/src/components/InfoBox/panelArt_dark.svg new file mode 100644 index 00000000000..82a26c49b84 --- /dev/null +++ b/packages/grafana-ui/src/components/InfoBox/panelArt_dark.svg @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/grafana-ui/src/components/InfoBox/panelArt_light.svg b/packages/grafana-ui/src/components/InfoBox/panelArt_light.svg new file mode 100644 index 00000000000..69a1a4611c1 --- /dev/null +++ b/packages/grafana-ui/src/components/InfoBox/panelArt_light.svg @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/grafana-ui/src/components/Layout/Layout.tsx b/packages/grafana-ui/src/components/Layout/Layout.tsx index 7f2b2ba97e4..3cc6c8e12b3 100644 --- a/packages/grafana-ui/src/components/Layout/Layout.tsx +++ b/packages/grafana-ui/src/components/Layout/Layout.tsx @@ -1,5 +1,5 @@ import React, { HTMLProps } from 'react'; -import { css } from 'emotion'; +import { css, cx } from 'emotion'; import { GrafanaTheme } from '@grafana/data'; import { stylesFactory, useTheme } from '../../themes'; @@ -24,6 +24,8 @@ export interface LayoutProps extends Omit, 'align' | ' export interface ContainerProps { padding?: Spacing; margin?: Spacing; + grow?: number; + shrink?: number; } export const Layout: React.FC = ({ @@ -84,10 +86,26 @@ export const VerticalGroup: React.FC> ); -export const Container: React.FC = ({ children, padding, margin }) => { +export const Container: React.FC = ({ children, padding, margin, grow, shrink }) => { const theme = useTheme(); const styles = getContainerStyles(theme, padding, margin); - return
{children}
; + return ( +
+ {children} +
+ ); }; const getStyles = stylesFactory( diff --git a/packages/grafana-ui/src/components/TransformersUI/FilterByNameTransformerEditor.tsx b/packages/grafana-ui/src/components/TransformersUI/FilterByNameTransformerEditor.tsx index 8f56a193604..e4c174221b4 100644 --- a/packages/grafana-ui/src/components/TransformersUI/FilterByNameTransformerEditor.tsx +++ b/packages/grafana-ui/src/components/TransformersUI/FilterByNameTransformerEditor.tsx @@ -190,5 +190,5 @@ export const filterFieldsByNameTransformRegistryItem: TransformerRegistyItem = { + [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]; diff --git a/public/app/features/dashboard/components/PanelEditor/FieldConfigEditor.tsx b/public/app/features/dashboard/components/PanelEditor/FieldConfigEditor.tsx index a1aa7cdee5c..c7db5ab7cb4 100644 --- a/public/app/features/dashboard/components/PanelEditor/FieldConfigEditor.tsx +++ b/public/app/features/dashboard/components/PanelEditor/FieldConfigEditor.tsx @@ -2,18 +2,20 @@ import React, { useCallback } from 'react'; import cloneDeep from 'lodash/cloneDeep'; import { DataFrame, + FeatureState, FieldConfigPropertyItem, FieldConfigSource, PanelPlugin, SelectableValue, VariableSuggestionsScope, } 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 { OverrideEditor } from './OverrideEditor'; import groupBy from 'lodash/groupBy'; import { OptionsGroup } from './OptionsGroup'; import { selectors } from '@grafana/e2e-selectors'; +import { css } from 'emotion'; interface Props { plugin: PanelPlugin; @@ -27,6 +29,8 @@ interface Props { * Expects the container div to have size set and will fill it 100% */ export const OverrideFieldConfigEditor: React.FC = props => { + const theme = useTheme(); + const { config } = props; const onOverrideChange = (index: number, override: any) => { const { config } = props; let overrides = cloneDeep(config.overrides); @@ -104,6 +108,19 @@ export const OverrideFieldConfigEditor: React.FC = props => { return (
+ {config.overrides.length === 0 && ( + + Field options overrides give you a fine grained control over how your data is displayed. + + )} + {renderOverrides()} {renderAddOverride()}
diff --git a/public/app/features/dashboard/components/TransformationsEditor/TransformationsEditor.tsx b/public/app/features/dashboard/components/TransformationsEditor/TransformationsEditor.tsx index f4ed8135f7e..f994426f5b1 100644 --- a/public/app/features/dashboard/components/TransformationsEditor/TransformationsEditor.tsx +++ b/public/app/features/dashboard/components/TransformationsEditor/TransformationsEditor.tsx @@ -1,8 +1,18 @@ 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 { DataFrame, DataTransformerConfig, + FeatureState, GrafanaTheme, SelectableValue, standardTransformersRegistry, @@ -114,13 +124,23 @@ export class TransformationsEditor extends React.PureComponent { renderNoAddedTransformsState() { return ( - <> -

- Transformations allow you to combine, re-order, hide and rename specific parts the the data set before being - visualized.
- Choose one of the transformations below to start with: -

- + + + +

+ Transformations allow you to join, calculate, re-order, hide and rename your query results before being + visualized.
+ Many transforms are not suitable if your using the Graph visualisation as it currently only supports time + series.
+ It can help to switch to Table visualisation to understand what a transformation is doing.
+

+

Select one of the transformations below to start.

+
+
{standardTransformersRegistry.list().map(t => { return ( @@ -136,7 +156,7 @@ export class TransformationsEditor extends React.PureComponent { ); })} - +
); } @@ -170,6 +190,11 @@ const getTransformationCardStyles = stylesFactory((theme: GrafanaTheme) => { border: none; 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 { background: ${theme.colors.bg3}; box-shadow: none; diff --git a/public/app/features/dashboard/panel_editor/VizTypePickerPlugin.tsx b/public/app/features/dashboard/panel_editor/VizTypePickerPlugin.tsx index f3d26eec83e..646c692ded1 100644 --- a/public/app/features/dashboard/panel_editor/VizTypePickerPlugin.tsx +++ b/public/app/features/dashboard/panel_editor/VizTypePickerPlugin.tsx @@ -1,9 +1,8 @@ import React from 'react'; 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 { selectors } from '@grafana/e2e-selectors'; -import { PanelPluginBadge } from '../../plugins/PluginSignatureBadge'; interface Props { isCurrent: boolean; @@ -126,3 +125,36 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => { }); export default VizTypePickerPlugin; + +interface PanelPluginBadgeProps { + plugin: PanelPluginMeta; +} +const PanelPluginBadge: React.FC = ({ plugin }) => { + const display = getPanelStateBadgeDisplayModel(plugin); + + if (plugin.state !== PluginState.deprecated && plugin.state !== PluginState.alpha) { + return null; + } + return ; +}; + +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'; diff --git a/public/app/features/plugins/PluginSignatureBadge.tsx b/public/app/features/plugins/PluginSignatureBadge.tsx index deb34df3cb7..308462d7dbe 100644 --- a/public/app/features/plugins/PluginSignatureBadge.tsx +++ b/public/app/features/plugins/PluginSignatureBadge.tsx @@ -1,61 +1,17 @@ import React from 'react'; -import { Icon, IconName, stylesFactory, Tooltip, useTheme } from '@grafana/ui'; -import { - getColorFromHexRgbOrName, - GrafanaTheme, - PanelPluginMeta, - PluginSignatureStatus, - PluginState, -} from '@grafana/data'; -import { css } from 'emotion'; -import tinycolor from 'tinycolor2'; +import { Badge, BadgeProps } from '@grafana/ui'; +import { PluginSignatureStatus } from '@grafana/data'; interface Props { status: PluginSignatureStatus; } export const PluginSignatureBadge: React.FC = ({ status }) => { - const theme = useTheme(); const display = getSignatureDisplayModel(status); - const styles = getStyles(theme, display); - - return ( - -
- - {display.text} -
-
- ); -}; - -interface PanelPluginBadgeProps { - plugin: PanelPluginMeta; -} -export const PanelPluginBadge: React.FC = ({ 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 ( -
- - {display.text} -
- ); + return ; }; -interface DisplayModel { - text: string; - icon: IconName; - color: string; - tooltip: string; -} - -function getSignatureDisplayModel(signature: PluginSignatureStatus): DisplayModel { +function getSignatureDisplayModel(signature: PluginSignatureStatus): BadgeProps { switch (signature) { case PluginSignatureStatus.internal: 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' }; } -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'; diff --git a/public/app/types/svg.d.ts b/public/app/types/svg.d.ts new file mode 100644 index 00000000000..cdb2b1a9a23 --- /dev/null +++ b/public/app/types/svg.d.ts @@ -0,0 +1,4 @@ +declare module '*.svg' { + const content: string; + export default content; +} diff --git a/public/test/mocks/svg.ts b/public/test/mocks/svg.ts new file mode 100644 index 00000000000..6fdb2b40b35 --- /dev/null +++ b/public/test/mocks/svg.ts @@ -0,0 +1 @@ +export const svg = 'svg'; diff --git a/scripts/webpack/webpack.common.js b/scripts/webpack/webpack.common.js index f7cc9d81e62..72d7afbb85b 100644 --- a/scripts/webpack/webpack.common.js +++ b/scripts/webpack/webpack.common.js @@ -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 diff --git a/scripts/webpack/webpack.dev.js b/scripts/webpack/webpack.dev.js index e1dd4c4fe23..e32e5ffb91e 100644 --- a/scripts/webpack/webpack.dev.js +++ b/scripts/webpack/webpack.dev.js @@ -86,10 +86,6 @@ module.exports = (env = {}) => sourceMap: false, preserveUrl: false, }), - { - test: /\.(png|jpg|gif|ttf|eot|svg|woff(2)?)(\?[a-z0-9=&.]+)?$/, - loader: 'file-loader', - }, ], },