grafana/public/app/features/manage-dashboards/DashboardImportPage.tsx
Ashley Harrison 47f8717149
React: Use new JSX transform (#88802)
* update eslint, tsconfig + esbuild to handle new jsx transform

* remove thing that breaks the new jsx transform

* remove react imports

* adjust grafana-icons build

* is this the correct syntax?

* try this

* well this was much easier than expected...

* change grafana-plugin-configs webpack config

* fixes

* fix lockfile

* fix 2 more violations

* use path.resolve instead of require.resolve

* remove react import

* fix react imports

* more fixes

* remove React import

* remove import React from docs

* remove another react import
2024-06-25 12:43:47 +01:00

271 lines
8.8 KiB
TypeScript

import { css } from '@emotion/css';
import { PureComponent } from 'react';
import { connect, ConnectedProps } from 'react-redux';
import { AppEvents, GrafanaTheme2, LoadingState, NavModelItem } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { config, reportInteraction } from '@grafana/runtime';
import {
Button,
Field,
Input,
Spinner,
stylesFactory,
TextArea,
Themeable2,
FileDropzone,
withTheme2,
DropzoneFile,
FileDropzoneDefaultChildren,
LinkButton,
TextLink,
Label,
Stack,
} from '@grafana/ui';
import appEvents from 'app/core/app_events';
import { Form } from 'app/core/components/Form/Form';
import { Page } from 'app/core/components/Page/Page';
import { t, Trans } from 'app/core/internationalization';
import { GrafanaRouteComponentProps } from 'app/core/navigation/types';
import { StoreState } from 'app/types';
import { cleanUpAction } from '../../core/actions/cleanUp';
import { ImportDashboardOverview } from './components/ImportDashboardOverview';
import { fetchGcomDashboard, importDashboardJson } from './state/actions';
import { initialImportDashboardState } from './state/reducers';
import { validateDashboardJson, validateGcomDashboard } from './utils/validation';
type DashboardImportPageRouteSearchParams = {
gcomDashboardId?: string;
};
type OwnProps = Themeable2 & GrafanaRouteComponentProps<{}, DashboardImportPageRouteSearchParams>;
const IMPORT_STARTED_EVENT_NAME = 'dashboard_import_loaded';
const JSON_PLACEHOLDER = `{
"title": "Example - Repeating Dictionary variables",
"uid": "_0HnEoN4z",
"panels": [...]
...
}
`;
const mapStateToProps = (state: StoreState) => ({
loadingState: state.importDashboard.state,
});
const mapDispatchToProps = {
fetchGcomDashboard,
importDashboardJson,
cleanUpAction,
};
const connector = connect(mapStateToProps, mapDispatchToProps);
type Props = OwnProps & ConnectedProps<typeof connector>;
class UnthemedDashboardImport extends PureComponent<Props> {
constructor(props: Props) {
super(props);
const { gcomDashboardId } = this.props.queryParams;
if (gcomDashboardId) {
this.getGcomDashboard({ gcomDashboard: gcomDashboardId });
return;
}
}
componentWillUnmount() {
this.props.cleanUpAction({ cleanupAction: (state) => (state.importDashboard = initialImportDashboardState) });
}
// Do not display upload file list
fileListRenderer = (file: DropzoneFile, removeFile: (file: DropzoneFile) => void) => null;
onFileUpload = (result: string | ArrayBuffer | null) => {
reportInteraction(IMPORT_STARTED_EVENT_NAME, {
import_source: 'json_uploaded',
});
try {
this.props.importDashboardJson(JSON.parse(String(result)));
} catch (error) {
if (error instanceof Error) {
appEvents.emit(AppEvents.alertError, ['Import failed', 'JSON -> JS Serialization failed: ' + error.message]);
}
return;
}
};
getDashboardFromJson = (formData: { dashboardJson: string }) => {
reportInteraction(IMPORT_STARTED_EVENT_NAME, {
import_source: 'json_pasted',
});
this.props.importDashboardJson(JSON.parse(formData.dashboardJson));
};
getGcomDashboard = (formData: { gcomDashboard: string }) => {
reportInteraction(IMPORT_STARTED_EVENT_NAME, {
import_source: 'gcom',
});
let dashboardId;
const match = /(^\d+$)|dashboards\/(\d+)/.exec(formData.gcomDashboard);
if (match && match[1]) {
dashboardId = match[1];
} else if (match && match[2]) {
dashboardId = match[2];
}
if (dashboardId) {
this.props.fetchGcomDashboard(dashboardId);
}
};
renderImportForm() {
const styles = importStyles(this.props.theme);
const GcomDashboardsLink = () => (
<TextLink variant="bodySmall" href="https://grafana.com/grafana/dashboards/" external>
grafana.com/dashboards
</TextLink>
);
return (
<>
<div className={styles.option}>
<FileDropzone
options={{ multiple: false, accept: ['.json', '.txt'] }}
readAs="readAsText"
fileListRenderer={this.fileListRenderer}
onLoad={this.onFileUpload}
>
<FileDropzoneDefaultChildren
primaryText={t('dashboard-import.file-dropzone.primary-text', 'Upload dashboard JSON file')}
secondaryText={t(
'dashboard-import.file-dropzone.secondary-text',
'Drag and drop here or click to browse'
)}
/>
</FileDropzone>
</div>
<div className={styles.option}>
<Form onSubmit={this.getGcomDashboard} defaultValues={{ gcomDashboard: '' }}>
{({ register, errors }) => (
<Field
label={
<Label className={styles.labelWithLink} htmlFor="url-input">
<span>
<Trans i18nKey="dashboard-import.gcom-field.label">
Find and import dashboards for common applications at <GcomDashboardsLink />
</Trans>
</span>
</Label>
}
invalid={!!errors.gcomDashboard}
error={errors.gcomDashboard && errors.gcomDashboard.message}
>
<Input
id="url-input"
placeholder={t('dashboard-import.gcom-field.placeholder', 'Grafana.com dashboard URL or ID')}
type="text"
{...register('gcomDashboard', {
required: t(
'dashboard-import.gcom-field.validation-required',
'A Grafana dashboard URL or ID is required'
),
validate: validateGcomDashboard,
})}
addonAfter={
<Button type="submit">
<Trans i18nKey="dashboard-import.gcom-field.load-button">Load</Trans>
</Button>
}
/>
</Field>
)}
</Form>
</div>
<div className={styles.option}>
<Form onSubmit={this.getDashboardFromJson} defaultValues={{ dashboardJson: '' }}>
{({ register, errors }) => (
<>
<Field
label={t('dashboard-import.json-field.label', 'Import via dashboard JSON model')}
invalid={!!errors.dashboardJson}
error={errors.dashboardJson && errors.dashboardJson.message}
>
<TextArea
{...register('dashboardJson', {
required: t('dashboard-import.json-field.validation-required', 'Need a dashboard JSON model'),
validate: validateDashboardJson,
})}
data-testid={selectors.components.DashboardImportPage.textarea}
id="dashboard-json-textarea"
rows={10}
placeholder={JSON_PLACEHOLDER}
/>
</Field>
<Stack>
<Button type="submit" data-testid={selectors.components.DashboardImportPage.submit}>
<Trans i18nKey="dashboard-import.form-actions.load">Load</Trans>
</Button>
<LinkButton variant="secondary" href={`${config.appSubUrl}/dashboards`}>
<Trans i18nKey="dashboard-import.form-actions.cancel">Cancel</Trans>
</LinkButton>
</Stack>
</>
)}
</Form>
</div>
</>
);
}
pageNav: NavModelItem = {
text: 'Import dashboard',
subTitle: 'Import dashboard from file or Grafana.com',
};
render() {
const { loadingState } = this.props;
return (
<Page navId="dashboards/browse" pageNav={this.pageNav}>
<Page.Contents>
{loadingState === LoadingState.Loading && (
<Stack direction={'column'} justifyContent="center">
<Stack justifyContent="center">
<Spinner size="xxl" />
</Stack>
</Stack>
)}
{[LoadingState.Error, LoadingState.NotStarted].includes(loadingState) && this.renderImportForm()}
{loadingState === LoadingState.Done && <ImportDashboardOverview />}
</Page.Contents>
</Page>
);
}
}
const DashboardImportUnConnected = withTheme2(UnthemedDashboardImport);
const DashboardImport = connector(DashboardImportUnConnected);
DashboardImport.displayName = 'DashboardImport';
export default DashboardImport;
const importStyles = stylesFactory((theme: GrafanaTheme2) => {
return {
option: css`
margin-bottom: ${theme.spacing(4)};
max-width: 600px;
`,
labelWithLink: css`
max-width: 100%;
`,
linkWithinLabel: css`
font-size: inherit;
`,
};
});