import {Component, Fragment} from 'react'; // eslint-disable-next-line no-restricted-imports import {withRouter, WithRouterProps} from 'react-router'; import * as Sentry from '@sentry/react'; import scrollToElement from 'scroll-to-element'; import {defined} from 'sentry/utils'; import {sanitizeQuerySelector} from 'sentry/utils/sanitizeQuerySelector'; import FormPanel from './formPanel'; import {Field, FieldObject, JsonFormObject} from './types'; type Props = { additionalFieldProps?: {[key: string]: any}; /** * If `forms` is not defined, `title` + `fields` must be required. * Allows more fine grain control of title/fields */ fields?: FieldObject[]; /** * Fields that are grouped by "section" */ forms?: JsonFormObject[]; } & WithRouterProps & Omit< React.ComponentProps, 'highlighted' | 'fields' | 'additionalFieldProps' >; type State = { // Field name that should be highlighted highlighted?: string; }; class JsonForm extends Component { state: State = { // location.hash is optional because of tests. highlighted: this.props.location?.hash, }; componentDidMount() { this.scrollToHash(); } componentDidUpdate(prevProps: Props) { if (this.props.location && this.props.location.hash !== prevProps.location.hash) { const hash = this.props.location.hash; this.scrollToHash(hash); this.setState({highlighted: hash}); } } scrollToHash(toHash?: string): void { // location.hash is optional because of tests. const hash = toHash || this.props.location?.hash; if (!hash) { return; } // Push onto callback queue so it runs after the DOM is updated, // this is required when navigating from a different page so that // the element is rendered on the page before trying to getElementById. try { scrollToElement(sanitizeQuerySelector(decodeURIComponent(hash)), { align: 'middle', offset: -100, }); } catch (err) { Sentry.captureException(err); } } shouldDisplayForm(fields: FieldObject[]) { const fieldsWithVisibleProp = fields.filter( field => typeof field !== 'function' && defined(field?.visible) ) as Array & Required>>; if (fields.length === fieldsWithVisibleProp.length) { const {additionalFieldProps, ...props} = this.props; const areAllFieldsHidden = fieldsWithVisibleProp.every(field => { if (typeof field.visible === 'function') { return !field.visible({...props, ...additionalFieldProps}); } return !field.visible; }); return !areAllFieldsHidden; } return true; } renderForm({ fields, formPanelProps, title, }: { fields: FieldObject[]; formPanelProps: Pick< Props, | 'access' | 'disabled' | 'features' | 'additionalFieldProps' | 'renderFooter' | 'renderHeader' > & Pick; title?: React.ReactNode; }) { const shouldDisplayForm = this.shouldDisplayForm(fields); if ( !shouldDisplayForm && !formPanelProps?.renderFooter && !formPanelProps?.renderHeader ) { return null; } return ; } render() { const { access, collapsible, fields, title, forms, disabled, features, additionalFieldProps, renderFooter, renderHeader, location: _location, ...otherProps } = this.props; const formPanelProps = { access, disabled, features, additionalFieldProps, renderFooter, renderHeader, highlighted: this.state.highlighted, collapsible, }; return (
{typeof forms !== 'undefined' && forms.map((formGroup, i) => ( {this.renderForm({formPanelProps, ...formGroup})} ))} {typeof forms === 'undefined' && typeof fields !== 'undefined' && this.renderForm({fields, formPanelProps, title})}
); } } export default withRouter(JsonForm);