Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding search and replace to datadocs #30

Merged
merged 2 commits into from
May 1, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions datahub/webapp/__tests__/lib/data-doc/search.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { replaceStringIndices } from 'lib/data-doc/search';

const testString = 'the quick brown fox jumps over the lazy dog';

test('Replace empty case', () => {
expect(replaceStringIndices(testString, [], 'ze')).toBe(
'the quick brown fox jumps over the lazy dog'
);
});

test('Replace simple case', () => {
expect(replaceStringIndices(testString, [[0, 3]], 'ze')).toBe(
'ze quick brown fox jumps over the lazy dog'
);
});

test('Replace multiple case', () => {
expect(
replaceStringIndices(
testString,
[
[0, 3],
[31, 34],
],
'le'
)
).toBe('le quick brown fox jumps over le lazy dog');
expect(
replaceStringIndices(
testString,
[
[16, 19],
[40, 43],
],
'tiger'
)
).toBe('the quick brown tiger jumps over the lazy tiger');
});
4 changes: 2 additions & 2 deletions datahub/webapp/__tests__/lib/utils/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as utils from 'lib/utils/index';

// missing getSelectionRect, download, copy, scrollToElement, smoothScroll
// missing getSelectionRect, download, copy, smoothScroll

test('removeEmpty', () => {
expect(utils.removeEmpty({ notempty: 'test', empty: null })).toStrictEqual({
Expand All @@ -18,7 +18,7 @@ test('titleize', () => {

test('sleep', () => {
const mockFunction = jest.fn(() => {
console.log('mock function runs');
// console.log('mock function runs');
});

const testFunction = async () => {
Expand Down
6 changes: 6 additions & 0 deletions datahub/webapp/components/DataDoc/DataDoc.scss
Original file line number Diff line number Diff line change
Expand Up @@ -207,4 +207,10 @@
}
}
}

.SearchAndReplaceBar {
position: fixed;
right: 60px;
z-index: 10;
}
}
177 changes: 132 additions & 45 deletions datahub/webapp/components/DataDoc/DataDoc.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import React from 'react';
import { connect } from 'react-redux';
import { ContentState } from 'draft-js';
import { findIndex, sample } from 'lodash';
import { findIndex } from 'lodash';
import { bind, debounce } from 'lodash-decorators';

import { decorate } from 'core-decorators';
import memoizeOne from 'memoize-one';
import classNames from 'classnames';

import { CELL_TYPE, IDataDoc, IDataCell } from 'const/datadoc';
import {
CELL_TYPE,
IDataDoc,
IDataCell,
DataCellUpdateFields,
} from 'const/datadoc';
import ds from 'lib/datasource';
import history from 'lib/router-history';
import { sendConfirm, sendNotification } from 'lib/dataHubUI';
Expand All @@ -34,7 +39,6 @@ import { DataDocRightSidebar } from 'components/DataDocRightSidebar/DataDocRight
import { DataDocUIGuide } from 'components/UIGuide/DataDocUIGuide';
import { DataDocCell } from 'components/DataDocCell/DataDocCell';

import { Title } from 'ui/Title/Title';
import { Message } from 'ui/Message/Message';
import { Loading } from 'ui/Loading/Loading';

Expand All @@ -44,8 +48,14 @@ import { DataDocError } from './DataDocError';
import { DataDocContentContainer } from './DataDocContentContainer';

import './DataDoc.scss';
import { DataDocLoading } from './DataDocLoading';

const loadingHints: string[] = require('config/loading_hints.yaml').hints;
import { searchDataDoc, replaceDataDoc } from 'lib/data-doc/search';
import {
ISearchAndReplaceHandles,
SearchAndReplace,
} from 'components/SearchAndReplace/SearchAndReplace';
import { ISearchOptions, ISearchResult } from 'const/searchAndReplace';

interface IOwnProps {
docId: number;
Expand All @@ -63,6 +73,8 @@ interface IState {
defaultCollapseAllCells: boolean;
cellIdToExecutionId: Record<number, number>;
highlightCellIndex?: number;

showSearchAndReplace: boolean;
}

class DataDocComponent extends React.Component<IProps, IState> {
Expand All @@ -74,8 +86,13 @@ class DataDocComponent extends React.Component<IProps, IState> {
connected: false,
defaultCollapseAllCells: null,
cellIdToExecutionId: {},

showSearchAndReplace: false,
};

//
public searchAndReplaceRef = React.createRef<ISearchAndReplaceHandles>();

public componentDidMount() {
this.autoFocusCell({}, this.props);
this.openDataDoc(this.props.docId);
Expand All @@ -95,14 +112,38 @@ class DataDocComponent extends React.Component<IProps, IState> {
this.setState({
defaultCollapseAllCells: null,
focusedCellIndex: null,

// Sharing State
cellIdToExecutionId: {},
highlightCellIndex: null,
});
// Reset search
this.searchAndReplaceRef.current?.reset();
}
this.openDataDoc(this.props.docId);
}

if (this.props.dataDoc !== prevProps.dataDoc && this.props.dataDoc) {
if (
this.props.dataDoc?.dataDocCells !== prevProps.dataDoc?.dataDocCells
) {
const cells = this.props.dataDoc?.dataDocCells ?? [];
const previousCells = prevProps.dataDoc?.dataDocCells ?? [];
const someCellsContextChanged =
cells.length !== previousCells.length ||
cells.some(
(cell, index) =>
cell.context !== previousCells[index].context
);

if (someCellsContextChanged) {
this.searchAndReplaceRef.current?.performSearch();
}
}

if (
this.props.dataDoc?.title !== prevProps.dataDoc?.title &&
this.props.dataDoc?.title
) {
this.publishDataDocTitle(this.props.dataDoc.title);
}
}
Expand Down Expand Up @@ -209,19 +250,21 @@ class DataDocComponent extends React.Component<IProps, IState> {
// This is to quickly snap to the element, and then in case
// if the cell above/below pushes the element out of view we
// try to scroll it back
scrollToCell(dataDoc.dataDocCells[cellIndex], 0).then(
() =>
this.setState(
{
highlightCellIndex: cellIndex,
},
() =>
scrollToCell(
dataDoc.dataDocCells[cellIndex],
200,
5
)
)
scrollToCell(
dataDoc.dataDocCells[cellIndex].id,
0
).then(() =>
this.setState(
{
highlightCellIndex: cellIndex,
},
() =>
scrollToCell(
dataDoc.dataDocCells[cellIndex].id,
200,
5
)
)
);
}
}
Expand All @@ -244,6 +287,35 @@ class DataDocComponent extends React.Component<IProps, IState> {
}
}

@bind
public getSearchResults(
searchString: string,
searchOptions: ISearchOptions
) {
return searchDataDoc(this.props.dataDoc, searchString, searchOptions);
}

@bind
public replace(
searchResultsToReplace: ISearchResult[],
replaceString: string
) {
return replaceDataDoc(
this.props.dataDoc,
searchResultsToReplace,
replaceString,
(cellId, context) => this.updateCell(cellId, { context })
);
}

@bind
public async jumpToResult(result: ISearchResult) {
const cellId = result?.cellId;
if (cellId != null) {
await scrollToCell(cellId, 0);
}
}

@bind
public async insertCellAt(
index: number,
Expand All @@ -268,6 +340,11 @@ class DataDocComponent extends React.Component<IProps, IState> {
}
}

@bind
public updateCell(cellId: number, fields: DataCellUpdateFields) {
return this.props.updateDataDocCell(this.props.docId, cellId, fields);
}

@bind
public handleToggleCollapse() {
this.setState(({ defaultCollapseAllCells }) => ({
Expand Down Expand Up @@ -320,6 +397,7 @@ class DataDocComponent extends React.Component<IProps, IState> {
},

insertCellAt: this.insertCellAt,
updateCell: this.updateCell,

defaultCollapse,
focusedCellIndex,
Expand Down Expand Up @@ -388,28 +466,6 @@ class DataDocComponent extends React.Component<IProps, IState> {
ds.save(`/datadoc/${this.props.docId}/run/`);
}

public makeDataDocLoadingDOM() {
// Get a random hint from list of hints
const hint = sample(loadingHints);

return (
<div className="datadoc-loading">
<div className="datadoc-loading-message">
<Title>
<i className="fa fa-spinner fa-pulse" />
&nbsp; Loading DataDoc
</Title>

<br />
<p className="subtitle">
<i className="far fa-lightbulb" />
&nbsp; Did you know: {hint}
</p>
</div>
</div>
);
}

@bind
public renderLazyDataDocCell(
cell: IDataCell,
Expand Down Expand Up @@ -499,7 +555,12 @@ class DataDocComponent extends React.Component<IProps, IState> {
changeDataDocMeta,
} = this.props;

const { connected, defaultCollapseAllCells } = this.state;
const {
connected,
defaultCollapseAllCells,

showSearchAndReplace,
} = this.state;

let docDOM = null;
let isSavingDataDoc = false;
Expand Down Expand Up @@ -540,7 +601,7 @@ class DataDocComponent extends React.Component<IProps, IState> {
</DataDocContentContainer>
);
} else {
docDOM = this.makeDataDocLoadingDOM();
docDOM = <DataDocLoading />;
}

const leftSideBar = (
Expand Down Expand Up @@ -571,9 +632,16 @@ class DataDocComponent extends React.Component<IProps, IState> {
key="data-hub-data-doc"
>
<DataDocContext.Provider value={this.getDataDocContextState()}>
{leftSideBar}
{docDOM}
{rightSideBar}
<SearchAndReplace
getSearchResults={this.getSearchResults}
jumpToResult={this.jumpToResult}
replace={this.replace}
ref={this.searchAndReplaceRef}
>
{leftSideBar}
{docDOM}
{rightSideBar}
</SearchAndReplace>
</DataDocContext.Provider>
</div>
);
Expand Down Expand Up @@ -659,6 +727,25 @@ function mapDispatchToProps(dispatch: Dispatch, ownProps: IOwnProps) {
meta
)
),

updateDataDocCell: (
docId: number,
cellId: number,
fields: DataCellUpdateFields
) => {
try {
return dispatch(
dataDocActions.updateDataDocCell(
docId,
cellId,
fields.context,
fields.meta
)
);
} catch (e) {
sendNotification(`Cannot update cell, reason: ${e}`);
}
},
};
}

Expand Down
26 changes: 26 additions & 0 deletions datahub/webapp/components/DataDoc/DataDocLoading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React from 'react';
import { sample } from 'lodash';
import { Title } from 'ui/Title/Title';

const loadingHints: string[] = require('config/loading_hints.yaml').hints;

export const DataDocLoading: React.FC = () => {
const hint = sample(loadingHints);

return (
<div className="datadoc-loading">
<div className="datadoc-loading-message">
<Title>
<i className="fa fa-spinner fa-pulse" />
&nbsp; Loading DataDoc
</Title>

<br />
<p className="subtitle">
<i className="far fa-lightbulb" />
&nbsp; Did you know: {hint}
</p>
</div>
</div>
);
};
Loading