Chore: convert some class components to functional (#112661)
* convert BigValue to functional component
* convert AbstractList
* Revert "convert AbstractList"
This reverts commit 673c22a81d.
* ignore and deprecate remaining list components
* convert NotFoundDisplay
* convert rectangle
* fix some test components
* convert thresholds.tsx
* convert SpanGraph to functional component
* convert InviteeRow to functional component
* converted InviteesTable to functional component
* convert PanelPluginError to functional components
* convert UserOrganizations
* convert UserSessions to a functional component
* convert UserTeams to a functional component
* convert CloudMonitoringCheatSheet to a functional component
* convert ConfigEditor a react functional component
* convert TestInfoTab to a functional component
* convert DebugPanel to a functional component
* fix types + BigValue story
This commit is contained in:
@@ -574,11 +574,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"packages/grafana-ui/src/components/BigValue/BigValue.tsx": {
|
||||
"react-prefer-function-component/react-prefer-function-component": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"packages/grafana-ui/src/components/Cascader/Cascader.tsx": {
|
||||
"react-prefer-function-component/react-prefer-function-component": {
|
||||
"count": 1
|
||||
@@ -780,21 +775,6 @@
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"packages/grafana-ui/src/components/List/AbstractList.tsx": {
|
||||
"react-prefer-function-component/react-prefer-function-component": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"packages/grafana-ui/src/components/List/InlineList.tsx": {
|
||||
"react-prefer-function-component/react-prefer-function-component": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"packages/grafana-ui/src/components/List/List.tsx": {
|
||||
"react-prefer-function-component/react-prefer-function-component": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"packages/grafana-ui/src/components/MatchersUI/FieldValueMatcher.tsx": {
|
||||
"@typescript-eslint/consistent-type-assertions": {
|
||||
"count": 1
|
||||
@@ -2028,16 +2008,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"public/app/features/canvas/elements/notFound.tsx": {
|
||||
"react-prefer-function-component/react-prefer-function-component": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"public/app/features/canvas/elements/rectangle.tsx": {
|
||||
"react-prefer-function-component/react-prefer-function-component": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"public/app/features/canvas/runtime/element.tsx": {
|
||||
"react-prefer-function-component/react-prefer-function-component": {
|
||||
"count": 1
|
||||
@@ -2748,11 +2718,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"public/app/features/dashboard/containers/DashboardPage.test.tsx": {
|
||||
"react-prefer-function-component/react-prefer-function-component": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"public/app/features/dashboard/containers/DashboardPage.tsx": {
|
||||
"@grafana/no-aria-label-selectors": {
|
||||
"count": 1
|
||||
@@ -2777,11 +2742,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"public/app/features/dashboard/containers/SoloPanelPage.test.tsx": {
|
||||
"react-prefer-function-component/react-prefer-function-component": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"public/app/features/dashboard/dashgrid/DashboardGrid.tsx": {
|
||||
"react-prefer-function-component/react-prefer-function-component": {
|
||||
"count": 1
|
||||
@@ -2992,11 +2952,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"public/app/features/dimensions/editors/ThresholdsEditor/thresholds.tsx": {
|
||||
"react-prefer-function-component/react-prefer-function-component": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"public/app/features/dimensions/editors/URLPickerTab.tsx": {
|
||||
"no-restricted-syntax": {
|
||||
"count": 2
|
||||
@@ -3082,11 +3037,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"public/app/features/explore/TraceView/components/TracePageHeader/SpanGraph/index.tsx": {
|
||||
"react-prefer-function-component/react-prefer-function-component": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"public/app/features/explore/TraceView/components/TracePageHeader/TracePageHeader.tsx": {
|
||||
"react-hooks/rules-of-hooks": {
|
||||
"count": 1
|
||||
@@ -3298,16 +3248,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"public/app/features/invites/InviteeRow.tsx": {
|
||||
"react-prefer-function-component/react-prefer-function-component": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"public/app/features/invites/InviteesTable.tsx": {
|
||||
"react-prefer-function-component/react-prefer-function-component": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"public/app/features/invites/SignupInvited.tsx": {
|
||||
"no-restricted-syntax": {
|
||||
"count": 4
|
||||
@@ -3458,11 +3398,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"public/app/features/panel/components/PanelPluginError.tsx": {
|
||||
"react-prefer-function-component/react-prefer-function-component": {
|
||||
"count": 3
|
||||
}
|
||||
},
|
||||
"public/app/features/panel/components/VizTypePicker/PanelTypeCard.tsx": {
|
||||
"@grafana/no-aria-label-selectors": {
|
||||
"count": 1
|
||||
@@ -3587,26 +3522,11 @@
|
||||
"count": 3
|
||||
}
|
||||
},
|
||||
"public/app/features/profile/UserOrganizations.tsx": {
|
||||
"react-prefer-function-component/react-prefer-function-component": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"public/app/features/profile/UserProfileEditForm.tsx": {
|
||||
"no-restricted-syntax": {
|
||||
"count": 3
|
||||
}
|
||||
},
|
||||
"public/app/features/profile/UserSessions.tsx": {
|
||||
"react-prefer-function-component/react-prefer-function-component": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"public/app/features/profile/UserTeams.tsx": {
|
||||
"react-prefer-function-component/react-prefer-function-component": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"public/app/features/provisioning/Config/ConfigFormGithubCollapse.tsx": {
|
||||
"no-restricted-syntax": {
|
||||
"count": 2
|
||||
@@ -4287,11 +4207,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"public/app/plugins/datasource/cloud-monitoring/components/CloudMonitoringCheatSheet.tsx": {
|
||||
"react-prefer-function-component/react-prefer-function-component": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"public/app/plugins/datasource/cloud-monitoring/components/ConfigEditor/ConfigEditor.tsx": {
|
||||
"react-prefer-function-component/react-prefer-function-component": {
|
||||
"count": 1
|
||||
@@ -4494,11 +4409,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"public/app/plugins/datasource/grafana-testdata-datasource/ConfigEditor.tsx": {
|
||||
"react-prefer-function-component/react-prefer-function-component": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"public/app/plugins/datasource/grafana-testdata-datasource/QueryEditor.tsx": {
|
||||
"@typescript-eslint/consistent-type-assertions": {
|
||||
"count": 3
|
||||
@@ -4510,11 +4420,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"public/app/plugins/datasource/grafana-testdata-datasource/TestInfoTab.tsx": {
|
||||
"react-prefer-function-component/react-prefer-function-component": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"public/app/plugins/datasource/grafana-testdata-datasource/components/CSVWaveEditor.tsx": {
|
||||
"react-prefer-function-component/react-prefer-function-component": {
|
||||
"count": 1
|
||||
@@ -5031,11 +4936,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"public/app/plugins/panel/debug/DebugPanel.tsx": {
|
||||
"react-prefer-function-component/react-prefer-function-component": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"public/app/plugins/panel/debug/EventBusLogger.tsx": {
|
||||
"@typescript-eslint/no-explicit-any": {
|
||||
"count": 2
|
||||
|
||||
@@ -29,27 +29,36 @@ const meta: Meta = {
|
||||
width: { control: { type: 'range', min: 200, max: 800 } },
|
||||
height: { control: { type: 'range', min: 200, max: 800 } },
|
||||
colorMode: {
|
||||
control: { type: 'select', options: [BigValueColorMode.Value, BigValueColorMode.Background] },
|
||||
control: {
|
||||
type: 'select',
|
||||
},
|
||||
options: [BigValueColorMode.Value, BigValueColorMode.Background],
|
||||
},
|
||||
graphMode: {
|
||||
control: { type: 'select' },
|
||||
options: [BigValueGraphMode.Area, BigValueGraphMode.None],
|
||||
},
|
||||
justifyMode: {
|
||||
control: { type: 'select' },
|
||||
options: [BigValueJustifyMode.Auto, BigValueJustifyMode.Center],
|
||||
},
|
||||
graphMode: { control: { type: 'select', options: [BigValueGraphMode.Area, BigValueGraphMode.None] } },
|
||||
justifyMode: { control: { type: 'select', options: [BigValueJustifyMode.Auto, BigValueJustifyMode.Center] } },
|
||||
textMode: {
|
||||
control: {
|
||||
type: 'radio',
|
||||
options: [
|
||||
BigValueTextMode.Auto,
|
||||
BigValueTextMode.Name,
|
||||
BigValueTextMode.ValueAndName,
|
||||
BigValueTextMode.None,
|
||||
BigValueTextMode.Value,
|
||||
],
|
||||
},
|
||||
options: [
|
||||
BigValueTextMode.Auto,
|
||||
BigValueTextMode.Name,
|
||||
BigValueTextMode.ValueAndName,
|
||||
BigValueTextMode.None,
|
||||
BigValueTextMode.Value,
|
||||
],
|
||||
},
|
||||
color: { control: 'color' },
|
||||
},
|
||||
};
|
||||
|
||||
interface StoryProps extends Partial<Props> {
|
||||
interface StoryProps extends Props {
|
||||
numeric: number;
|
||||
title: string;
|
||||
color: string;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { cx } from '@emotion/css';
|
||||
import { PureComponent } from 'react';
|
||||
import * as React from 'react';
|
||||
import { memo, type MouseEventHandler } from 'react';
|
||||
|
||||
import { DisplayValue, DisplayValueAlignmentFactors, FieldSparkline } from '@grafana/data';
|
||||
import { PercentChangeColorMode, VizTextDisplayOptions } from '@grafana/schema';
|
||||
@@ -51,7 +50,7 @@ export interface Props extends Themeable2 {
|
||||
/** Sparkline values for showing a graph under/behind the value */
|
||||
sparkline?: FieldSparkline;
|
||||
/** onClick handler for the value */
|
||||
onClick?: React.MouseEventHandler<HTMLElement>;
|
||||
onClick?: MouseEventHandler<HTMLElement>;
|
||||
/** Custom styling */
|
||||
className?: string;
|
||||
/** Color mode for coloring the value or the background */
|
||||
@@ -83,58 +82,55 @@ export interface Props extends Themeable2 {
|
||||
disableWideLayout?: boolean;
|
||||
}
|
||||
|
||||
export class BigValue extends PureComponent<Props> {
|
||||
static defaultProps: Partial<Props> = {
|
||||
justifyMode: BigValueJustifyMode.Auto,
|
||||
};
|
||||
export const BigValue = memo<Props>((props) => {
|
||||
const { onClick, className, hasLinks, theme, justifyMode = BigValueJustifyMode.Auto } = props;
|
||||
|
||||
render() {
|
||||
const { onClick, className, hasLinks, theme } = this.props;
|
||||
const layout = buildLayout(this.props);
|
||||
const panelStyles = layout.getPanelStyles();
|
||||
const valueAndTitleContainerStyles = layout.getValueAndTitleContainerStyles();
|
||||
const valueStyles = layout.getValueStyles();
|
||||
const titleStyles = layout.getTitleStyles();
|
||||
const textValues = layout.textValues;
|
||||
const percentChange = this.props.value.percentChange;
|
||||
const percentChangeColorMode = this.props.percentChangeColorMode;
|
||||
const showPercentChange = percentChange != null && !Number.isNaN(percentChange);
|
||||
const layout = buildLayout({ ...props, justifyMode });
|
||||
const panelStyles = layout.getPanelStyles();
|
||||
const valueAndTitleContainerStyles = layout.getValueAndTitleContainerStyles();
|
||||
const valueStyles = layout.getValueStyles();
|
||||
const titleStyles = layout.getTitleStyles();
|
||||
const textValues = layout.textValues;
|
||||
const percentChange = props.value.percentChange;
|
||||
const percentChangeColorMode = props.percentChangeColorMode;
|
||||
const showPercentChange = percentChange != null && !Number.isNaN(percentChange);
|
||||
|
||||
// When there is an outer data link this tooltip will override the outer native tooltip
|
||||
const tooltip = hasLinks ? undefined : textValues.tooltip;
|
||||
|
||||
if (!onClick) {
|
||||
return (
|
||||
<div className={className} style={panelStyles} title={tooltip}>
|
||||
<div style={valueAndTitleContainerStyles}>
|
||||
{textValues.title && <div style={titleStyles}>{textValues.title}</div>}
|
||||
<FormattedValueDisplay value={textValues} style={valueStyles} />
|
||||
{showPercentChange && (
|
||||
<PercentChange
|
||||
percentChange={percentChange}
|
||||
styles={layout.getPercentChangeStyles(percentChange, percentChangeColorMode, valueStyles)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{layout.renderChart()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
// When there is an outer data link this tooltip will override the outer native tooltip
|
||||
const tooltip = hasLinks ? undefined : textValues.tooltip;
|
||||
|
||||
if (!onClick) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className={cx(clearButtonStyles(theme), className)}
|
||||
style={panelStyles}
|
||||
onClick={onClick}
|
||||
title={tooltip}
|
||||
>
|
||||
<div className={className} style={panelStyles} title={tooltip}>
|
||||
<div style={valueAndTitleContainerStyles}>
|
||||
{textValues.title && <div style={titleStyles}>{textValues.title}</div>}
|
||||
<FormattedValueDisplay value={textValues} style={valueStyles} />
|
||||
{showPercentChange && (
|
||||
<PercentChange
|
||||
percentChange={percentChange}
|
||||
styles={layout.getPercentChangeStyles(percentChange, percentChangeColorMode, valueStyles)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{layout.renderChart()}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className={cx(clearButtonStyles(theme), className)}
|
||||
style={panelStyles}
|
||||
onClick={onClick}
|
||||
title={tooltip}
|
||||
>
|
||||
<div style={valueAndTitleContainerStyles}>
|
||||
{textValues.title && <div style={titleStyles}>{textValues.title}</div>}
|
||||
<FormattedValueDisplay value={textValues} style={valueStyles} />
|
||||
</div>
|
||||
{layout.renderChart()}
|
||||
</button>
|
||||
);
|
||||
});
|
||||
|
||||
BigValue.displayName = 'BigValue';
|
||||
|
||||
@@ -26,6 +26,9 @@ const getStyles = stylesFactory((inlineList = false) => ({
|
||||
}),
|
||||
}));
|
||||
|
||||
/** @deprecated Use ul/li/arr.map directly instead */
|
||||
// no point converting, this is deprecated
|
||||
// eslint-disable-next-line react-prefer-function-component/react-prefer-function-component
|
||||
export class AbstractList<T> extends PureComponent<AbstractListProps<T>> {
|
||||
constructor(props: AbstractListProps<T>) {
|
||||
super(props);
|
||||
|
||||
@@ -3,6 +3,8 @@ import { PureComponent } from 'react';
|
||||
import { ListProps, AbstractList } from './AbstractList';
|
||||
|
||||
/** @deprecated Use ul/li/arr.map directly instead */
|
||||
// no point converting, this is deprecated
|
||||
// eslint-disable-next-line react-prefer-function-component/react-prefer-function-component
|
||||
export class InlineList<T> extends PureComponent<ListProps<T>> {
|
||||
render() {
|
||||
return <AbstractList inline {...this.props} />;
|
||||
|
||||
@@ -3,6 +3,8 @@ import { PureComponent } from 'react';
|
||||
import { ListProps, AbstractList } from './AbstractList';
|
||||
|
||||
/** @deprecated Use ul/li/arr.map directly instead */
|
||||
// no point converting, this is deprecated
|
||||
// eslint-disable-next-line react-prefer-function-component/react-prefer-function-component
|
||||
export class List<T> extends PureComponent<ListProps<T>> {
|
||||
render() {
|
||||
return <AbstractList {...this.props} />;
|
||||
|
||||
@@ -1,25 +1,24 @@
|
||||
import { PureComponent } from 'react';
|
||||
import { memo } from 'react';
|
||||
|
||||
import { Trans } from '@grafana/i18n';
|
||||
|
||||
import { CanvasElementItem, CanvasElementProps } from '../element';
|
||||
|
||||
class NotFoundDisplay extends PureComponent<CanvasElementProps> {
|
||||
render() {
|
||||
const { config } = this.props;
|
||||
return (
|
||||
<div>
|
||||
<Trans
|
||||
i18nKey="canvas.not-found-display.not-found"
|
||||
components={{ config: <pre>{JSON.stringify(config, null, 2)}</pre> }}
|
||||
>
|
||||
<h3>Not found: </h3>
|
||||
{'<config />'}
|
||||
</Trans>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
const NotFoundDisplay = memo(({ config }: CanvasElementProps) => {
|
||||
return (
|
||||
<div>
|
||||
<Trans
|
||||
i18nKey="canvas.not-found-display.not-found"
|
||||
components={{ config: <pre>{JSON.stringify(config, null, 2)}</pre> }}
|
||||
>
|
||||
<h3>Not found: </h3>
|
||||
{'<config />'}
|
||||
</Trans>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
NotFoundDisplay.displayName = 'NotFoundDisplay';
|
||||
|
||||
export const notFoundItem: CanvasElementItem = {
|
||||
id: 'not-found',
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { PureComponent } from 'react';
|
||||
import { memo } from 'react';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { t } from '@grafana/i18n';
|
||||
import { stylesFactory } from '@grafana/ui';
|
||||
import { config } from 'app/core/config';
|
||||
import { useStyles2 } from '@grafana/ui';
|
||||
import { DimensionContext } from 'app/features/dimensions/context';
|
||||
import { ColorDimensionEditor } from 'app/features/dimensions/editors/ColorDimensionEditor';
|
||||
import { TextDimensionEditor } from 'app/features/dimensions/editors/TextDimensionEditor';
|
||||
@@ -18,20 +17,19 @@ import {
|
||||
} from '../element';
|
||||
import { Align, TextConfig, TextData, VAlign } from '../types';
|
||||
|
||||
class RectangleDisplay extends PureComponent<CanvasElementProps<TextConfig, TextData>> {
|
||||
render() {
|
||||
const { data } = this.props;
|
||||
const styles = getStyles(config.theme2, data);
|
||||
const RectangleDisplay = memo<CanvasElementProps<TextConfig, TextData>>(({ data }) => {
|
||||
const styles = useStyles2(getStyles, data);
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<span className={styles.span}>{data?.text}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<span className={styles.span}>{data?.text}</span>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
const getStyles = stylesFactory((theme: GrafanaTheme2, data) => ({
|
||||
RectangleDisplay.displayName = 'RectangleDisplay';
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2, data: TextData | undefined) => ({
|
||||
container: css({
|
||||
position: 'absolute',
|
||||
height: '100%',
|
||||
@@ -45,7 +43,7 @@ const getStyles = stylesFactory((theme: GrafanaTheme2, data) => ({
|
||||
fontSize: `${data?.size}px`,
|
||||
color: data?.color,
|
||||
}),
|
||||
}));
|
||||
});
|
||||
|
||||
export const rectangleItem: CanvasElementItem<TextConfig, TextData> = {
|
||||
id: 'rectangle',
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { screen, waitFor } from '@testing-library/react';
|
||||
import { KBarProvider } from 'kbar';
|
||||
import { Component } from 'react';
|
||||
import { useEffectOnce } from 'react-use';
|
||||
import { mockToolkitActionCreator } from 'test/core/redux/mocks';
|
||||
import { render } from 'test/test-utils';
|
||||
@@ -34,11 +33,9 @@ jest.mock('app/features/dashboard/dashgrid/LazyLoader', () => {
|
||||
});
|
||||
|
||||
jest.mock('app/features/dashboard/components/DashboardSettings/GeneralSettings', () => {
|
||||
class GeneralSettings extends Component<{}, {}> {
|
||||
render() {
|
||||
return <>general settings</>;
|
||||
}
|
||||
}
|
||||
const GeneralSettings = () => {
|
||||
return <>general settings</>;
|
||||
};
|
||||
|
||||
return { GeneralSettings };
|
||||
});
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { Component } from 'react';
|
||||
import { getGrafanaContextMock } from 'test/mocks/getGrafanaContextMock';
|
||||
|
||||
import { Dashboard } from '@grafana/schema';
|
||||
@@ -16,12 +15,10 @@ import { Props, SoloPanelPage } from './SoloPanelPage';
|
||||
|
||||
jest.mock('app/features/dashboard/components/DashboardSettings/GeneralSettings', () => ({}));
|
||||
jest.mock('app/features/dashboard/dashgrid/DashboardPanel', () => {
|
||||
class DashboardPanel extends Component<DashboardPanelProps> {
|
||||
render() {
|
||||
// In this test we only check whether a new panel has arrived in the props
|
||||
return <>{this.props.panel?.title}</>;
|
||||
}
|
||||
}
|
||||
const DashboardPanel = (props: DashboardPanelProps) => {
|
||||
// In this test we only check whether a new panel has arrived in the props
|
||||
return <>{props.panel?.title}</>;
|
||||
};
|
||||
|
||||
return { DashboardPanel };
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PureComponent } from 'react';
|
||||
import { memo } from 'react';
|
||||
|
||||
import { StandardEditorProps, ThresholdsConfig, ThresholdsMode, ThresholdsFieldConfigSettings } from '@grafana/data';
|
||||
|
||||
@@ -6,25 +6,17 @@ import { ThresholdsEditor } from './ThresholdsEditor';
|
||||
|
||||
type Props = StandardEditorProps<ThresholdsConfig, ThresholdsFieldConfigSettings>;
|
||||
|
||||
export class ThresholdsValueEditor extends PureComponent<Props> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
}
|
||||
export const ThresholdsValueEditor = memo(({ value, onChange }: Props) => {
|
||||
const thresholdsValue = value ?? {
|
||||
mode: ThresholdsMode.Percentage,
|
||||
|
||||
render() {
|
||||
const { onChange } = this.props;
|
||||
let value = this.props.value;
|
||||
if (!value) {
|
||||
value = {
|
||||
mode: ThresholdsMode.Percentage,
|
||||
// Must be sorted by 'value', first value is always -Infinity
|
||||
steps: [
|
||||
// anything?
|
||||
],
|
||||
};
|
||||
|
||||
// Must be sorted by 'value', first value is always -Infinity
|
||||
steps: [
|
||||
// anything?
|
||||
],
|
||||
};
|
||||
}
|
||||
return <ThresholdsEditor thresholds={thresholdsValue} onChange={onChange} />;
|
||||
});
|
||||
|
||||
return <ThresholdsEditor thresholds={value} onChange={onChange} />;
|
||||
}
|
||||
}
|
||||
ThresholdsValueEditor.displayName = 'ThresholdsValueEditor';
|
||||
|
||||
+9
-10
@@ -14,7 +14,7 @@
|
||||
|
||||
import { css } from '@emotion/css';
|
||||
import memoizeOne from 'memoize-one';
|
||||
import * as React from 'react';
|
||||
import { memo } from 'react';
|
||||
|
||||
import { ViewRange, TUpdateViewRangeTimeFunction, ViewRangeTimeUpdate } from '../../TraceTimelineViewer/types';
|
||||
import { Trace, TraceSpan } from '../../types/trace';
|
||||
@@ -65,13 +65,8 @@ function getItems(trace: Trace): SpanItem[] {
|
||||
|
||||
const memoizedGetitems = memoizeOne(getItems);
|
||||
|
||||
export default class SpanGraph extends React.PureComponent<SpanGraphProps> {
|
||||
static defaultProps = {
|
||||
height: DEFAULT_HEIGHT,
|
||||
};
|
||||
|
||||
render() {
|
||||
const { height, trace, viewRange, updateNextViewRangeTime, updateViewRangeTime } = this.props;
|
||||
const SpanGraph = memo(
|
||||
({ height = DEFAULT_HEIGHT, trace, viewRange, updateNextViewRangeTime, updateViewRangeTime }: SpanGraphProps) => {
|
||||
const styles = getStyles();
|
||||
|
||||
if (!trace) {
|
||||
@@ -87,7 +82,7 @@ export default class SpanGraph extends React.PureComponent<SpanGraphProps> {
|
||||
<ViewingLayer
|
||||
viewRange={viewRange}
|
||||
numTicks={TIMELINE_TICK_INTERVAL}
|
||||
height={height || DEFAULT_HEIGHT}
|
||||
height={height}
|
||||
updateViewRangeTime={updateViewRangeTime}
|
||||
updateNextViewRangeTime={updateNextViewRangeTime}
|
||||
/>
|
||||
@@ -95,4 +90,8 @@ export default class SpanGraph extends React.PureComponent<SpanGraphProps> {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
SpanGraph.displayName = 'SpanGraph';
|
||||
|
||||
export default SpanGraph;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PureComponent } from 'react';
|
||||
import { memo } from 'react';
|
||||
import { connect, ConnectedProps } from 'react-redux';
|
||||
|
||||
import { t } from '@grafana/i18n';
|
||||
@@ -19,31 +19,30 @@ interface OwnProps {
|
||||
|
||||
export type Props = OwnProps & ConnectedProps<typeof connector>;
|
||||
|
||||
class InviteeRow extends PureComponent<Props> {
|
||||
render() {
|
||||
const { invitee, revokeInvite } = this.props;
|
||||
return (
|
||||
<tr>
|
||||
<td>{invitee.email}</td>
|
||||
<td>{invitee.name}</td>
|
||||
<td className="text-right">
|
||||
<ClipboardButton icon="copy" variant="secondary" size="sm" getText={() => invitee.url}>
|
||||
Copy Invite
|
||||
</ClipboardButton>
|
||||
|
||||
</td>
|
||||
<td>
|
||||
<Button
|
||||
variant="destructive"
|
||||
size="sm"
|
||||
icon="times"
|
||||
onClick={() => revokeInvite(invitee.code)}
|
||||
aria-label={t('invites.invitee-row.aria-label-revoke-invite', 'Revoke invite')}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
}
|
||||
const InviteeRow = memo<Props>(({ invitee, revokeInvite }) => {
|
||||
return (
|
||||
<tr>
|
||||
<td>{invitee.email}</td>
|
||||
<td>{invitee.name}</td>
|
||||
<td className="text-right">
|
||||
<ClipboardButton icon="copy" variant="secondary" size="sm" getText={() => invitee.url}>
|
||||
Copy Invite
|
||||
</ClipboardButton>
|
||||
|
||||
</td>
|
||||
<td>
|
||||
<Button
|
||||
variant="destructive"
|
||||
size="sm"
|
||||
icon="times"
|
||||
onClick={() => revokeInvite(invitee.code)}
|
||||
aria-label={t('invites.invitee-row.aria-label-revoke-invite', 'Revoke invite')}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
});
|
||||
|
||||
InviteeRow.displayName = 'InviteeRow';
|
||||
|
||||
export default connector(InviteeRow);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PureComponent } from 'react';
|
||||
import { memo } from 'react';
|
||||
|
||||
import { Trans } from '@grafana/i18n';
|
||||
import { Invitee } from 'app/types/user';
|
||||
@@ -9,30 +9,30 @@ export interface Props {
|
||||
invitees: Invitee[];
|
||||
}
|
||||
|
||||
export default class InviteesTable extends PureComponent<Props> {
|
||||
render() {
|
||||
const { invitees } = this.props;
|
||||
const InviteesTable = memo(({ invitees }: Props) => {
|
||||
return (
|
||||
<table className="filter-table form-inline">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<Trans i18nKey="invites.invitees-table.email">Email</Trans>
|
||||
</th>
|
||||
<th>
|
||||
<Trans i18nKey="invites.invitees-table.name">Name</Trans>
|
||||
</th>
|
||||
<th />
|
||||
<th style={{ width: '34px' }} />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody data-testid="InviteesTable-body">
|
||||
{invitees.map((invitee, index) => {
|
||||
return <InviteeRow key={`${invitee.id}-${index}`} invitee={invitee} />;
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<table className="filter-table form-inline">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<Trans i18nKey="invites.invitees-table.email">Email</Trans>
|
||||
</th>
|
||||
<th>
|
||||
<Trans i18nKey="invites.invitees-table.name">Name</Trans>
|
||||
</th>
|
||||
<th />
|
||||
<th style={{ width: '34px' }} />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody data-testid="InviteesTable-body">
|
||||
{invitees.map((invitee, index) => {
|
||||
return <InviteeRow key={`${invitee.id}-${index}`} invitee={invitee} />;
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
}
|
||||
InviteesTable.displayName = 'InviteesTable';
|
||||
|
||||
export default InviteesTable;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Libraries
|
||||
import { PureComponent, ReactNode } from 'react';
|
||||
import { memo, ReactNode } from 'react';
|
||||
|
||||
// Types
|
||||
import { PanelProps, PanelPlugin, PluginType, PanelPluginMeta } from '@grafana/data';
|
||||
@@ -13,44 +13,41 @@ interface Props {
|
||||
text?: ReactNode;
|
||||
}
|
||||
|
||||
class PanelPluginError extends PureComponent<Props> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
}
|
||||
const PanelPluginError = memo<Props>(({ title, text }) => {
|
||||
const style = {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
height: '100%',
|
||||
};
|
||||
|
||||
render() {
|
||||
const style = {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
height: '100%',
|
||||
};
|
||||
return (
|
||||
<div style={style}>
|
||||
<Alert severity={AppNotificationSeverity.Error} title={title}>
|
||||
{text}
|
||||
</Alert>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div style={style}>
|
||||
<Alert severity={AppNotificationSeverity.Error} {...this.props} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
PanelPluginError.displayName = 'PanelPluginError';
|
||||
|
||||
export function getPanelPluginLoadError(meta: PanelPluginMeta, err: unknown): PanelPlugin {
|
||||
const LoadError = class LoadError extends PureComponent<PanelProps> {
|
||||
render() {
|
||||
const text = (
|
||||
<Trans i18nKey="panel.panel-plugin-error.text-load-error">
|
||||
Check the server startup logs for more information. <br />
|
||||
If this plugin was loaded from Git, then make sure it was compiled.
|
||||
</Trans>
|
||||
);
|
||||
return (
|
||||
<PanelPluginError
|
||||
title={t('panel.panel-plugin-error.title-load-error', 'Error loading: {{panelId}}', { panelId: meta.id })}
|
||||
text={text}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
const LoadError = memo<PanelProps>(() => {
|
||||
const text = (
|
||||
<Trans i18nKey="panel.panel-plugin-error.text-load-error">
|
||||
Check the server startup logs for more information. <br />
|
||||
If this plugin was loaded from Git, then make sure it was compiled.
|
||||
</Trans>
|
||||
);
|
||||
return (
|
||||
<PanelPluginError
|
||||
title={t('panel.panel-plugin-error.title-load-error', 'Error loading: {{panelId}}', { panelId: meta.id })}
|
||||
text={text}
|
||||
/>
|
||||
);
|
||||
});
|
||||
LoadError.displayName = 'LoadError';
|
||||
const plugin = new PanelPlugin(LoadError);
|
||||
plugin.meta = meta;
|
||||
plugin.loadError = true;
|
||||
@@ -58,15 +55,14 @@ export function getPanelPluginLoadError(meta: PanelPluginMeta, err: unknown): Pa
|
||||
}
|
||||
|
||||
export function getPanelPluginNotFound(id: string, silent?: boolean): PanelPlugin {
|
||||
const NotFound = class NotFound extends PureComponent<PanelProps> {
|
||||
render() {
|
||||
return (
|
||||
<PanelPluginError
|
||||
title={t('panel.panel-plugin-error.title-not-found', 'Panel plugin not found: {{id}}', { id })}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
const NotFound = memo<PanelProps>(() => {
|
||||
return (
|
||||
<PanelPluginError
|
||||
title={t('panel.panel-plugin-error.title-not-found', 'Panel plugin not found: {{id}}', { id })}
|
||||
/>
|
||||
);
|
||||
});
|
||||
NotFound.displayName = 'NotFound';
|
||||
|
||||
const plugin = new PanelPlugin(silent ? () => null : NotFound);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PureComponent } from 'react';
|
||||
import { memo } from 'react';
|
||||
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { Trans, t } from '@grafana/i18n';
|
||||
@@ -12,73 +12,71 @@ export interface Props {
|
||||
setUserOrg: (org: UserOrg) => void;
|
||||
}
|
||||
|
||||
export class UserOrganizations extends PureComponent<Props> {
|
||||
render() {
|
||||
const { isLoading, orgs, user } = this.props;
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<LoadingPlaceholder
|
||||
text={t('profile.user-organizations.text-loading-organizations', 'Loading organizations...')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (orgs.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
export const UserOrganizations = memo<Props>(({ isLoading, orgs, user, setUserOrg }) => {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div>
|
||||
<h3 className="page-sub-heading">
|
||||
<Trans i18nKey="user-orgs.title">Organizations</Trans>
|
||||
</h3>
|
||||
|
||||
<ScrollContainer overflowY="visible" overflowX="auto" width="100%">
|
||||
<table className="filter-table form-inline" data-testid={selectors.components.UserProfile.orgsTable}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<Trans i18nKey="user-orgs.name-column">Name</Trans>
|
||||
</th>
|
||||
<th>
|
||||
<Trans i18nKey="user-orgs.role-column">Role</Trans>
|
||||
</th>
|
||||
<th />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{orgs.map((org: UserOrg, index) => {
|
||||
return (
|
||||
<tr key={index}>
|
||||
<td>{org.name}</td>
|
||||
<td>{org.role}</td>
|
||||
<td className="text-right">
|
||||
{org.orgId === user?.orgId ? (
|
||||
<Button variant="secondary" size="sm" disabled>
|
||||
<Trans i18nKey="user-orgs.current-org-button">Current</Trans>
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
this.props.setUserOrg(org);
|
||||
}}
|
||||
>
|
||||
<Trans i18nKey="user-orgs.select-org-button">Select organisation</Trans>
|
||||
</Button>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</ScrollContainer>
|
||||
</div>
|
||||
<LoadingPlaceholder
|
||||
text={t('profile.user-organizations.text-loading-organizations', 'Loading organizations...')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (orgs.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h3 className="page-sub-heading">
|
||||
<Trans i18nKey="user-orgs.title">Organizations</Trans>
|
||||
</h3>
|
||||
|
||||
<ScrollContainer overflowY="visible" overflowX="auto" width="100%">
|
||||
<table className="filter-table form-inline" data-testid={selectors.components.UserProfile.orgsTable}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<Trans i18nKey="user-orgs.name-column">Name</Trans>
|
||||
</th>
|
||||
<th>
|
||||
<Trans i18nKey="user-orgs.role-column">Role</Trans>
|
||||
</th>
|
||||
<th />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{orgs.map((org: UserOrg, index) => {
|
||||
return (
|
||||
<tr key={index}>
|
||||
<td>{org.name}</td>
|
||||
<td>{org.role}</td>
|
||||
<td className="text-right">
|
||||
{org.orgId === user?.orgId ? (
|
||||
<Button variant="secondary" size="sm" disabled>
|
||||
<Trans i18nKey="user-orgs.current-org-button">Current</Trans>
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setUserOrg(org);
|
||||
}}
|
||||
>
|
||||
<Trans i18nKey="user-orgs.select-org-button">Select organisation</Trans>
|
||||
</Button>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</ScrollContainer>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
UserOrganizations.displayName = 'UserOrganizations';
|
||||
|
||||
export default UserOrganizations;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { PureComponent } from 'react';
|
||||
import { memo } from 'react';
|
||||
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { Trans, t } from '@grafana/i18n';
|
||||
import { Button, Icon, LoadingPlaceholder, ScrollContainer } from '@grafana/ui';
|
||||
import { Button, Icon, LoadingPlaceholder, ScrollContainer, useStyles2 } from '@grafana/ui';
|
||||
import { TagBadge } from 'app/core/components/TagFilter/TagBadge';
|
||||
import { formatDate } from 'app/core/internationalization/dates';
|
||||
import { UserSession } from 'app/types/user';
|
||||
@@ -14,90 +14,89 @@ interface Props {
|
||||
revokeUserSession: (tokenId: number) => void;
|
||||
}
|
||||
|
||||
class UserSessions extends PureComponent<Props> {
|
||||
render() {
|
||||
const { isLoading, sessions, revokeUserSession } = this.props;
|
||||
const styles = getStyles();
|
||||
const UserSessions = memo<Props>(({ isLoading, sessions, revokeUserSession }) => {
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
if (isLoading) {
|
||||
return <LoadingPlaceholder text={<Trans i18nKey="user-sessions.loading">Loading sessions...</Trans>} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.wrapper}>
|
||||
{sessions.length > 0 && (
|
||||
<>
|
||||
<h3 className="page-sub-heading">
|
||||
<Trans i18nKey="profile.user-sessions.sessions">Sessions</Trans>
|
||||
</h3>
|
||||
<ScrollContainer overflowY="visible" overflowX="auto" width="100%">
|
||||
<table className="filter-table form-inline" data-testid={selectors.components.UserProfile.sessionsTable}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<Trans i18nKey="user-session.seen-at-column">Last seen</Trans>
|
||||
</th>
|
||||
<th>
|
||||
<Trans i18nKey="user-session.created-at-column">Logged on</Trans>
|
||||
</th>
|
||||
<th>
|
||||
<Trans i18nKey="user-session.ip-column">IP address</Trans>
|
||||
</th>
|
||||
<th>
|
||||
<Trans i18nKey="user-session.browser-column">Browser & OS</Trans>
|
||||
</th>
|
||||
<th>
|
||||
<Trans i18nKey="user-session.identity-provider-column">Identity Provider</Trans>
|
||||
</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{sessions.map((session: UserSession, index) => (
|
||||
<tr key={index}>
|
||||
{session.isActive ? (
|
||||
<td>
|
||||
<Trans i18nKey="profile.user-sessions.now">Now</Trans>
|
||||
</td>
|
||||
) : (
|
||||
<td>{session.seenAt}</td>
|
||||
)}
|
||||
<td>{formatDate(session.createdAt, { dateStyle: 'long' })}</td>
|
||||
<td>{session.clientIp}</td>
|
||||
<td>
|
||||
<Trans
|
||||
i18nKey="profile.user-sessions.browser-details"
|
||||
values={{ browser: session.browser, os: session.os, osVersion: session.osVersion }}
|
||||
>
|
||||
{'{{browser}}'} on {'{{os}}'} {'{{osVersion}}'}
|
||||
</Trans>
|
||||
</td>
|
||||
<td>
|
||||
{session.authModule && <TagBadge label={session.authModule} removeIcon={false} count={0} />}
|
||||
</td>
|
||||
<td>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="destructive"
|
||||
tooltip={t('user-session.revoke', 'Revoke user session')}
|
||||
onClick={() => revokeUserSession(session.id)}
|
||||
aria-label={t('user-session.revoke', 'Revoke user session')}
|
||||
>
|
||||
<Icon name="power" />
|
||||
</Button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</ScrollContainer>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
if (isLoading) {
|
||||
return <LoadingPlaceholder text={<Trans i18nKey="user-sessions.loading">Loading sessions...</Trans>} />;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.wrapper}>
|
||||
{sessions.length > 0 && (
|
||||
<>
|
||||
<h3 className="page-sub-heading">
|
||||
<Trans i18nKey="profile.user-sessions.sessions">Sessions</Trans>
|
||||
</h3>
|
||||
<ScrollContainer overflowY="visible" overflowX="auto" width="100%">
|
||||
<table className="filter-table form-inline" data-testid={selectors.components.UserProfile.sessionsTable}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<Trans i18nKey="user-session.seen-at-column">Last seen</Trans>
|
||||
</th>
|
||||
<th>
|
||||
<Trans i18nKey="user-session.created-at-column">Logged on</Trans>
|
||||
</th>
|
||||
<th>
|
||||
<Trans i18nKey="user-session.ip-column">IP address</Trans>
|
||||
</th>
|
||||
<th>
|
||||
<Trans i18nKey="user-session.browser-column">Browser & OS</Trans>
|
||||
</th>
|
||||
<th>
|
||||
<Trans i18nKey="user-session.identity-provider-column">Identity Provider</Trans>
|
||||
</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{sessions.map((session: UserSession, index) => (
|
||||
<tr key={index}>
|
||||
{session.isActive ? (
|
||||
<td>
|
||||
<Trans i18nKey="profile.user-sessions.now">Now</Trans>
|
||||
</td>
|
||||
) : (
|
||||
<td>{session.seenAt}</td>
|
||||
)}
|
||||
<td>{formatDate(session.createdAt, { dateStyle: 'long' })}</td>
|
||||
<td>{session.clientIp}</td>
|
||||
<td>
|
||||
<Trans
|
||||
i18nKey="profile.user-sessions.browser-details"
|
||||
values={{ browser: session.browser, os: session.os, osVersion: session.osVersion }}
|
||||
>
|
||||
{'{{browser}}'} on {'{{os}}'} {'{{osVersion}}'}
|
||||
</Trans>
|
||||
</td>
|
||||
<td>
|
||||
{session.authModule && <TagBadge label={session.authModule} removeIcon={false} count={0} />}
|
||||
</td>
|
||||
<td>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="destructive"
|
||||
tooltip={t('user-session.revoke', 'Revoke user session')}
|
||||
onClick={() => revokeUserSession(session.id)}
|
||||
aria-label={t('user-session.revoke', 'Revoke user session')}
|
||||
>
|
||||
<Icon name="power" />
|
||||
</Button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</ScrollContainer>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
UserSessions.displayName = 'UserSessions';
|
||||
|
||||
const getStyles = () => ({
|
||||
wrapper: css({
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PureComponent } from 'react';
|
||||
import { memo } from 'react';
|
||||
|
||||
import { Trans, t } from '@grafana/i18n';
|
||||
import { LoadingPlaceholder, ScrollContainer } from '@grafana/ui';
|
||||
@@ -9,61 +9,59 @@ export interface Props {
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
export class UserTeams extends PureComponent<Props> {
|
||||
render() {
|
||||
const { isLoading, teams } = this.props;
|
||||
|
||||
if (isLoading) {
|
||||
return <LoadingPlaceholder text={t('profile.user-teams.text-loading-teams', 'Loading teams...')} />;
|
||||
}
|
||||
|
||||
if (teams.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h3 className="page-sub-heading">
|
||||
<Trans i18nKey="profile.user-teams.teams">Teams</Trans>
|
||||
</h3>
|
||||
<ScrollContainer overflowY="visible" overflowX="auto" width="100%">
|
||||
<table
|
||||
className="filter-table form-inline"
|
||||
aria-label={t('profile.user-teams.aria-label-user-teams-table', 'User teams table')}
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th />
|
||||
<th>
|
||||
<Trans i18nKey="profile.user-teams.name">Name</Trans>
|
||||
</th>
|
||||
<th>
|
||||
<Trans i18nKey="profile.user-teams.email">Email</Trans>
|
||||
</th>
|
||||
<th>
|
||||
<Trans i18nKey="profile.user-teams.members">Members</Trans>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{teams.map((team: Team, index) => {
|
||||
return (
|
||||
<tr key={index}>
|
||||
<td className="width-4 text-center">
|
||||
<img className="filter-table__avatar" src={team.avatarUrl} alt="" />
|
||||
</td>
|
||||
<td>{team.name}</td>
|
||||
<td>{team.email}</td>
|
||||
<td>{team.memberCount}</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</ScrollContainer>
|
||||
</div>
|
||||
);
|
||||
export const UserTeams = memo<Props>(({ isLoading, teams }) => {
|
||||
if (isLoading) {
|
||||
return <LoadingPlaceholder text={t('profile.user-teams.text-loading-teams', 'Loading teams...')} />;
|
||||
}
|
||||
}
|
||||
|
||||
if (teams.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h3 className="page-sub-heading">
|
||||
<Trans i18nKey="profile.user-teams.teams">Teams</Trans>
|
||||
</h3>
|
||||
<ScrollContainer overflowY="visible" overflowX="auto" width="100%">
|
||||
<table
|
||||
className="filter-table form-inline"
|
||||
aria-label={t('profile.user-teams.aria-label-user-teams-table', 'User teams table')}
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th />
|
||||
<th>
|
||||
<Trans i18nKey="profile.user-teams.name">Name</Trans>
|
||||
</th>
|
||||
<th>
|
||||
<Trans i18nKey="profile.user-teams.email">Email</Trans>
|
||||
</th>
|
||||
<th>
|
||||
<Trans i18nKey="profile.user-teams.members">Members</Trans>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{teams.map((team: Team, index) => {
|
||||
return (
|
||||
<tr key={index}>
|
||||
<td className="width-4 text-center">
|
||||
<img className="filter-table__avatar" src={team.avatarUrl} alt="" />
|
||||
</td>
|
||||
<td>{team.name}</td>
|
||||
<td>{team.email}</td>
|
||||
<td>{team.memberCount}</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</ScrollContainer>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
UserTeams.displayName = 'UserTeams';
|
||||
|
||||
export default UserTeams;
|
||||
|
||||
+8
-7
@@ -1,15 +1,12 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { PureComponent } from 'react';
|
||||
import { memo } from 'react';
|
||||
|
||||
import { QueryEditorHelpProps } from '@grafana/data';
|
||||
|
||||
import { CloudMonitoringQuery } from '../types/query';
|
||||
|
||||
export default class CloudMonitoringCheatSheet extends PureComponent<
|
||||
QueryEditorHelpProps<CloudMonitoringQuery>,
|
||||
{ userExamples: string[] }
|
||||
> {
|
||||
render() {
|
||||
const CloudMonitoringCheatSheet = memo(
|
||||
({ query, datasource, onClickExample }: QueryEditorHelpProps<CloudMonitoringQuery>) => {
|
||||
return (
|
||||
<div>
|
||||
<h2>Cloud Monitoring alias patterns</h2>
|
||||
@@ -75,4 +72,8 @@ export default class CloudMonitoringCheatSheet extends PureComponent<
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
CloudMonitoringCheatSheet.displayName = 'CloudMonitoringCheatSheet';
|
||||
|
||||
export default CloudMonitoringCheatSheet;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Libraries
|
||||
import { PureComponent } from 'react';
|
||||
import { memo } from 'react';
|
||||
|
||||
import { DataSourcePluginOptionsEditorProps } from '@grafana/data';
|
||||
|
||||
@@ -8,8 +8,8 @@ type Props = DataSourcePluginOptionsEditorProps;
|
||||
/**
|
||||
* Empty Config Editor -- settings to save
|
||||
*/
|
||||
export class ConfigEditor extends PureComponent<Props> {
|
||||
render() {
|
||||
return <div />;
|
||||
}
|
||||
}
|
||||
export const ConfigEditor = memo<Props>(() => {
|
||||
return <div />;
|
||||
});
|
||||
|
||||
ConfigEditor.displayName = 'ConfigEditor';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Libraries
|
||||
import { PureComponent } from 'react';
|
||||
import { memo } from 'react';
|
||||
|
||||
import { PluginConfigPageProps, DataSourcePluginMeta, DataSourceJsonData } from '@grafana/data';
|
||||
import { LinkButton } from '@grafana/ui';
|
||||
@@ -8,27 +8,23 @@ import { LinkButton } from '@grafana/ui';
|
||||
|
||||
interface Props extends PluginConfigPageProps<DataSourcePluginMeta<DataSourceJsonData>> {}
|
||||
|
||||
export class TestInfoTab extends PureComponent<Props> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
}
|
||||
export const TestInfoTab = memo<Props>(() => {
|
||||
return (
|
||||
<div>
|
||||
See github for more information about setting up a reproducible test environment.
|
||||
<br />
|
||||
<br />
|
||||
<LinkButton
|
||||
variant="secondary"
|
||||
href="https://github.com/grafana/grafana/tree/main/devenv"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
GitHub
|
||||
</LinkButton>
|
||||
<br />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
See github for more information about setting up a reproducible test environment.
|
||||
<br />
|
||||
<br />
|
||||
<LinkButton
|
||||
variant="secondary"
|
||||
href="https://github.com/grafana/grafana/tree/main/devenv"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
GitHub
|
||||
</LinkButton>
|
||||
<br />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
TestInfoTab.displayName = 'TestInfoTab';
|
||||
|
||||
@@ -9,6 +9,7 @@ import { TimeRangeUpdatedEvent } from '@grafana/runtime';
|
||||
import {
|
||||
Alert,
|
||||
BigValue,
|
||||
BigValueColorMode,
|
||||
BigValueGraphMode,
|
||||
BigValueJustifyMode,
|
||||
BigValueTextMode,
|
||||
@@ -226,6 +227,7 @@ function UnifiedAlertList(props: PanelProps<UnifiedAlertListOptions>) {
|
||||
<section>
|
||||
{props.options.viewMode === ViewMode.Stat && (
|
||||
<BigValue
|
||||
colorMode={BigValueColorMode.None}
|
||||
width={props.width}
|
||||
height={props.height}
|
||||
graphMode={BigValueGraphMode.None}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { Component } from 'react';
|
||||
|
||||
import { PanelProps } from '@grafana/data';
|
||||
|
||||
import { CursorView } from './CursorView';
|
||||
@@ -10,21 +8,19 @@ import { Options, DebugMode } from './panelcfg.gen';
|
||||
|
||||
type Props = PanelProps<Options>;
|
||||
|
||||
export class DebugPanel extends Component<Props> {
|
||||
render() {
|
||||
const { options } = this.props;
|
||||
export function DebugPanel(props: Props) {
|
||||
const { options } = props;
|
||||
|
||||
switch (options.mode) {
|
||||
case DebugMode.Events:
|
||||
return <EventBusLoggerPanel eventBus={this.props.eventBus} />;
|
||||
case DebugMode.Cursor:
|
||||
return <CursorView eventBus={this.props.eventBus} />;
|
||||
case DebugMode.State:
|
||||
return <StateView {...this.props} />;
|
||||
case DebugMode.ThrowError:
|
||||
throw new Error('I failed you and for that i am deeply sorry');
|
||||
default:
|
||||
return <RenderInfoViewer {...this.props} />;
|
||||
}
|
||||
switch (options.mode) {
|
||||
case DebugMode.Events:
|
||||
return <EventBusLoggerPanel eventBus={props.eventBus} />;
|
||||
case DebugMode.Cursor:
|
||||
return <CursorView eventBus={props.eventBus} />;
|
||||
case DebugMode.State:
|
||||
return <StateView {...props} />;
|
||||
case DebugMode.ThrowError:
|
||||
throw new Error('I failed you and for that i am deeply sorry');
|
||||
default:
|
||||
return <RenderInfoViewer {...props} />;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user