Unverified Commit 675a29de authored by Daniel's avatar Daniel Committed by GitHub

Update EditableText component (#299)

* Fix some styling issues with react-markdown
* Update EditableText to use React Refs instead of ref callbacks
* Add configs for table and column description max lengths
parent 1945ca62
import * as React from 'react'; import * as React from 'react';
import moment from 'moment-timezone';
import { OverlayTrigger, Popover } from 'react-bootstrap'; import { OverlayTrigger, Popover } from 'react-bootstrap';
import moment from 'moment-timezone';
import AppConfig from 'config/config';
import ColumnDescEditableText from 'components/TableDetail/ColumnDescEditableText'; import ColumnDescEditableText from 'components/TableDetail/ColumnDescEditableText';
import { logClick } from 'ducks/utilMethods'; import { logClick } from 'ducks/utilMethods';
import { TableColumn } from 'interfaces'; import { TableColumn } from 'interfaces';
...@@ -132,6 +132,7 @@ class DetailListItem extends React.Component<DetailListItemProps, DetailListItem ...@@ -132,6 +132,7 @@ class DetailListItem extends React.Component<DetailListItemProps, DetailListItem
columnIndex={this.props.index} columnIndex={this.props.index}
editable={metadata.is_editable} editable={metadata.is_editable}
value={metadata.description} value={metadata.description}
maxLength={AppConfig.editableText.columnDescLength}
/> />
</div> </div>
{ {
......
...@@ -17,9 +17,8 @@ ...@@ -17,9 +17,8 @@
padding-right: 32px; padding-right: 32px;
} }
// TODO - move to common styles .truncated .editable-text,
.truncated, .truncated .editable-text p {
.truncated .editable-text {
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
......
...@@ -356,6 +356,7 @@ export class TableDetail extends React.Component<TableDetailProps & RouteCompone ...@@ -356,6 +356,7 @@ export class TableDetail extends React.Component<TableDetailProps & RouteCompone
<TableDescEditableText <TableDescEditableText
value={ data.table_description } value={ data.table_description }
editable={ data.is_editable } editable={ data.is_editable }
maxLength={ AppConfig.editableText.tableDescLength }
/> />
</div> </div>
<div className="col-xs-12 col-md-5 float-md-right col-lg-4"> <div className="col-xs-12 col-md-5 float-md-right col-lg-4">
......
import autosize from 'autosize';
import * as React from 'react'; import * as React from 'react';
import ReactDOM from 'react-dom'; import { Overlay, Tooltip } from 'react-bootstrap';
import * as ReactMarkdown from 'react-markdown'; import * as ReactMarkdown from 'react-markdown';
import { Overlay, Popover, Tooltip } from 'react-bootstrap';
import autosize from 'autosize';
// TODO: Use css-modules instead of 'import' // TODO: Use css-modules instead of 'import'
// TODO: Outdated approach (lines 148, 168). Replace with React.createRef(). See more at https://reactjs.org/docs/refs-and-the-dom.html
import './styles.scss'; import './styles.scss';
export interface StateFromProps { export interface StateFromProps {
...@@ -34,12 +32,12 @@ interface EditableTextState { ...@@ -34,12 +32,12 @@ interface EditableTextState {
} }
class EditableText extends React.Component<EditableTextProps, EditableTextState> { class EditableText extends React.Component<EditableTextProps, EditableTextState> {
private textAreaTarget: HTMLTextAreaElement; private textAreaRef;
private editAnchorTarget: HTMLAnchorElement; private editAnchorRef;
public static defaultProps: EditableTextProps = { public static defaultProps: EditableTextProps = {
editable: true, editable: true,
maxLength: 4000, maxLength: 250,
onSubmitValue: null, onSubmitValue: null,
getLatestValue: null, getLatestValue: null,
value: '', value: '',
...@@ -52,6 +50,9 @@ class EditableText extends React.Component<EditableTextProps, EditableTextState> ...@@ -52,6 +50,9 @@ class EditableText extends React.Component<EditableTextProps, EditableTextState>
constructor(props) { constructor(props) {
super(props); super(props);
this.textAreaRef = React.createRef();
this.editAnchorRef = React.createRef();
this.state = { this.state = {
editable: props.editable, editable: props.editable,
inEditMode: false, inEditMode: false,
...@@ -63,21 +64,18 @@ class EditableText extends React.Component<EditableTextProps, EditableTextState> ...@@ -63,21 +64,18 @@ class EditableText extends React.Component<EditableTextProps, EditableTextState>
componentDidUpdate() { componentDidUpdate() {
const { isDisabled, inEditMode, refreshValue, value } = this.state; const { isDisabled, inEditMode, refreshValue, value } = this.state;
if (inEditMode) { const textArea = this.textAreaRef.current;
autosize(this.textAreaTarget); if (!inEditMode) return;
autosize(textArea);
if (refreshValue && refreshValue !== value && !isDisabled) { if (refreshValue && refreshValue !== value && !isDisabled) {
// disable the component if a refresh is needed // disable the component if a refresh is needed
this.setState({ isDisabled: true }) this.setState({ isDisabled: true })
} } else if (textArea) {
else {
// when entering edit mode, place focus in the textarea // when entering edit mode, place focus in the textarea
const textArea = ReactDOM.findDOMNode(this.textAreaTarget);
if (textArea) {
textArea.focus(); textArea.focus();
} }
} }
}
}
exitEditMode = () => { exitEditMode = () => {
this.setState({ isDisabled: false, inEditMode: false, refreshValue: '' }); this.setState({ isDisabled: false, inEditMode: false, refreshValue: '' });
...@@ -97,28 +95,27 @@ class EditableText extends React.Component<EditableTextProps, EditableTextState> ...@@ -97,28 +95,27 @@ class EditableText extends React.Component<EditableTextProps, EditableTextState>
}; };
updateText = () => { updateText = () => {
const newValue = ReactDOM.findDOMNode(this.textAreaTarget).value; const newValue = this.textAreaRef.current.value;
const onSuccessCallback = () => { this.setState({value: newValue, inEditMode: false, refreshValue: undefined }); }; const onSuccessCallback = () => { this.setState({value: newValue, inEditMode: false, refreshValue: undefined }); };
const onFailureCallback = () => { this.exitEditMode(); }; const onFailureCallback = () => { this.exitEditMode(); };
this.props.onSubmitValue(newValue, onSuccessCallback, onFailureCallback); this.props.onSubmitValue(newValue, onSuccessCallback, onFailureCallback);
}; };
getTarget(type) { getAnchorTarget = () => {
if (type === 'editAnchor') { return this.editAnchorRef.current;
return ReactDOM.findDOMNode(this.editAnchorTarget); };
}
if (type === 'textArea') { getTextAreaTarget = () => {
return ReactDOM.findDOMNode(this.textAreaTarget) return this.textAreaRef.current;
} };
}
render() { render() {
if (!this.state.editable) { if (!this.state.editable) {
return ( return (
<div id='editable-container' className='editable-container'> <div id='editable-container' className='editable-container'>
<div id='editable-text' className='editable-text'> <div id='editable-text' className='editable-text'>
<ReactMarkdown source={this.state.value}/> <ReactMarkdown source={ this.state.value }/>
</div> </div>
</div> </div>
); );
...@@ -128,26 +125,24 @@ class EditableText extends React.Component<EditableTextProps, EditableTextState> ...@@ -128,26 +125,24 @@ class EditableText extends React.Component<EditableTextProps, EditableTextState>
<div id='editable-container' className='editable-container'> <div id='editable-container' className='editable-container'>
<Overlay <Overlay
placement='top' placement='top'
show={this.state.isDisabled} show={ this.state.isDisabled }
target={this.getTarget.bind(this,'editAnchor')} target={ this.getAnchorTarget }
> >
<Tooltip id='error-tooltip'> <Tooltip id='error-tooltip'>
<div className="error-tooltip"> <div className="error-tooltip">
<text>This text is out of date, please refresh the component</text> <text>This text is out of date, please refresh the component</text>
<button onClick={this.refreshText} className="btn btn-flat-icon"> <button onClick={ this.refreshText } className="btn btn-flat-icon">
<img className='icon icon-refresh'/> <img className='icon icon-refresh'/>
</button> </button>
</div> </div>
</Tooltip> </Tooltip>
</Overlay> </Overlay>
<div id='editable-text' className={"editable-text"}> <div id='editable-text' className="editable-text">
<ReactMarkdown source={this.state.value}/> <ReactMarkdown source={ this.state.value }/>
<a className={ "edit-link" + (this.state.value ? "" : " no-value") } <a className={ "edit-link" + (this.state.value ? "" : " no-value") }
href="JavaScript:void(0)" href="JavaScript:void(0)"
onClick={ this.enterEditMode } onClick={ this.enterEditMode }
ref={ anchor => { ref={ this.editAnchorRef }
this.editAnchorTarget = anchor;
}}
> >
{ {
this.state.value ? "edit" : "Add Description" this.state.value ? "edit" : "Add Description"
...@@ -163,21 +158,17 @@ class EditableText extends React.Component<EditableTextProps, EditableTextState> ...@@ -163,21 +158,17 @@ class EditableText extends React.Component<EditableTextProps, EditableTextState>
<textarea <textarea
id='editable-textarea' id='editable-textarea'
className='editable-textarea' className='editable-textarea'
rows={2} rows={ 2 }
maxLength={this.props.maxLength} maxLength={ this.props.maxLength }
ref={textarea => { ref={ this.textAreaRef }
this.textAreaTarget = textarea; defaultValue={ this.state.value }
}} />
>
{this.state.value}
</textarea>
<Overlay <Overlay
placement='top' placement='top'
show={true} show={ true }
target={this.getTarget.bind(this,'textArea')} target={ this.getTextAreaTarget }
> >
<Tooltip> <Tooltip id='save-tooltip'>
<button id='cancel' onClick={this.exitEditMode}>Cancel</button> <button id='cancel' onClick={this.exitEditMode}>Cancel</button>
<button id='save' onClick={this.updateText}>Save</button> <button id='save' onClick={this.updateText}>Save</button>
</Tooltip> </Tooltip>
......
...@@ -25,6 +25,12 @@ ...@@ -25,6 +25,12 @@
.editable-text { .editable-text {
flex-grow: 0.1; flex-grow: 0.1;
word-break: break-word; word-break: break-word;
// React-Markdown wraps editable text with a paragraph
> p {
margin: 0;
display: inline;
}
} }
.editable-textarea { .editable-textarea {
......
...@@ -5,11 +5,18 @@ const configDefault: AppConfig = { ...@@ -5,11 +5,18 @@ const configDefault: AppConfig = {
curatedTags: [], curatedTags: [],
showAllTags: true, showAllTags: true,
}, },
editableText: {
tableDescLength: 750,
columnDescLength: 250,
},
google: { google: {
enabled: false, enabled: false,
key: 'default-key', key: 'default-key',
sampleRate: 100, sampleRate: 100,
}, },
indexUsers: {
enabled: false,
},
logoPath: null, logoPath: null,
navLinks: [ navLinks: [
{ {
...@@ -40,9 +47,6 @@ const configDefault: AppConfig = { ...@@ -40,9 +47,6 @@ const configDefault: AppConfig = {
return `https://DEFAULT_EXPLORE_URL?schema=${schema}&cluster=${cluster}&db=${database}&table=${table}`; return `https://DEFAULT_EXPLORE_URL?schema=${schema}&cluster=${cluster}&db=${database}&table=${table}`;
} }
}, },
indexUsers: {
enabled: false,
}
}; };
export default configDefault; export default configDefault;
...@@ -6,22 +6,24 @@ ...@@ -6,22 +6,24 @@
export interface AppConfig { export interface AppConfig {
browse: BrowseConfig; browse: BrowseConfig;
editableText: EditableTextConfig;
google: GoogleAnalyticsConfig; google: GoogleAnalyticsConfig;
indexUsers: IndexUsersConfig;
logoPath: string | null; logoPath: string | null;
navLinks: Array<LinkConfig>; navLinks: Array<LinkConfig>;
tableLineage: TableLineageConfig; tableLineage: TableLineageConfig;
tableProfile: TableProfileConfig; tableProfile: TableProfileConfig;
indexUsers: indexUsersConfig;
} }
export interface AppConfigCustom { export interface AppConfigCustom {
browse?: BrowseConfig; browse?: BrowseConfig;
editableText?: EditableTextConfig;
google?: GoogleAnalyticsConfig google?: GoogleAnalyticsConfig
indexUsers?: IndexUsersConfig;
logoPath?: string; logoPath?: string;
navLinks?: Array<LinkConfig>; navLinks?: Array<LinkConfig>;
tableLineage?: TableLineageConfig; tableLineage?: TableLineageConfig;
tableProfile?: TableProfileConfig; tableProfile?: TableProfileConfig;
indexUsers?: indexUsersConfig;
} }
/** /**
...@@ -83,6 +85,23 @@ export interface LinkConfig { ...@@ -83,6 +85,23 @@ export interface LinkConfig {
use_router: boolean; use_router: boolean;
} }
interface indexUsersConfig { /**
* IndexUsersConfig - When enabled, the IndexUsers feature will index users as searchable resources. This requires
* user objects are ingested via Databuilder
*
* enabled - Enables/disables this feature in the frontend only
*/
interface IndexUsersConfig {
enabled: boolean; enabled: boolean;
} }
/**
* EditableTextConfig - Configure max length limits for editable fields
*
* tableDescLength - maxlength for table descriptions
* columnDescLength - maxlength for column descriptions
*/
interface EditableTextConfig {
tableDescLength: number;
columnDescLength: number;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment