mirror of https://github.com/apache/druid.git
Web-Console: add clipboard modal (#7964)
* add clipboard modal * rename button * remove console.log * fix off by one * update tests * update snapshot * fix casing * update snapshot
This commit is contained in:
parent
8788849bab
commit
f1270c14f8
|
@ -4,28 +4,28 @@ exports[`table cell matches snapshot array long 1`] = `
|
|||
<span
|
||||
class="table-cell truncated"
|
||||
>
|
||||
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16
|
||||
[0, 1, 2, 3, 4, 5, 6, 7
|
||||
<span
|
||||
class="omitted"
|
||||
>
|
||||
...323 omitted...
|
||||
...357 omitted...
|
||||
</span>
|
||||
7, 98, 99]
|
||||
<span
|
||||
class="bp3-icon bp3-icon-clipboard action-icon"
|
||||
icon="clipboard"
|
||||
class="bp3-icon bp3-icon-more action-icon"
|
||||
icon="more"
|
||||
>
|
||||
<svg
|
||||
data-icon="clipboard"
|
||||
data-icon="more"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
width="16"
|
||||
>
|
||||
<desc>
|
||||
clipboard
|
||||
more
|
||||
</desc>
|
||||
<path
|
||||
d="M11 2c0-.55-.45-1-1-1h.22C9.88.4 9.24 0 8.5 0S7.12.4 6.78 1H7c-.55 0-1 .45-1 1v1h5V2zm2 0h-1v2H5V2H4c-.55 0-1 .45-1 1v12c0 .55.45 1 1 1h9c.55 0 1-.45 1-1V3c0-.55-.45-1-1-1z"
|
||||
d="M2 6.03a2 2 0 1 0 0 4 2 2 0 1 0 0-4zM14 6.03a2 2 0 1 0 0 4 2 2 0 1 0 0-4zM8 6.03a2 2 0 1 0 0 4 2 2 0 1 0 0-4z"
|
||||
fill-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
|
@ -57,28 +57,28 @@ exports[`table cell matches snapshot truncate 1`] = `
|
|||
<span
|
||||
class="table-cell truncated"
|
||||
>
|
||||
testtesttesttesttesttesttesttesttesttesttesttesttesttestt
|
||||
testtesttesttesttesttes
|
||||
<span
|
||||
class="omitted"
|
||||
>
|
||||
...329 omitted...
|
||||
...363 omitted...
|
||||
</span>
|
||||
sttesttest
|
||||
<span
|
||||
class="bp3-icon bp3-icon-clipboard action-icon"
|
||||
icon="clipboard"
|
||||
class="bp3-icon bp3-icon-more action-icon"
|
||||
icon="more"
|
||||
>
|
||||
<svg
|
||||
data-icon="clipboard"
|
||||
data-icon="more"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
width="16"
|
||||
>
|
||||
<desc>
|
||||
clipboard
|
||||
more
|
||||
</desc>
|
||||
<path
|
||||
d="M11 2c0-.55-.45-1-1-1h.22C9.88.4 9.24 0 8.5 0S7.12.4 6.78 1H7c-.55 0-1 .45-1 1v1h5V2zm2 0h-1v2H5V2H4c-.55 0-1 .45-1 1v12c0 .55.45 1 1 1h9c.55 0 1-.45 1-1V3c0-.55-.45-1-1-1z"
|
||||
d="M2 6.03a2 2 0 1 0 0 4 2 2 0 1 0 0-4zM14 6.03a2 2 0 1 0 0 4 2 2 0 1 0 0-4zM8 6.03a2 2 0 1 0 0 4 2 2 0 1 0 0-4z"
|
||||
fill-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
|
|
|
@ -47,6 +47,7 @@
|
|||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
color: #f5f8fa;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ export interface NullTableCellProps extends React.Props<any> {
|
|||
value?: any;
|
||||
timestamp?: boolean;
|
||||
unparseable?: boolean;
|
||||
openModal?: (str: string) => void;
|
||||
}
|
||||
|
||||
interface ShortParts {
|
||||
|
@ -41,8 +42,8 @@ interface ShortParts {
|
|||
export class TableCell extends React.PureComponent<NullTableCellProps> {
|
||||
static MAX_CHARS_TO_SHOW = 50;
|
||||
|
||||
static possiblyTruncate(str: string): React.ReactNode {
|
||||
if (str.length < TableCell.MAX_CHARS_TO_SHOW) return str;
|
||||
possiblyTruncate(str: string): React.ReactNode {
|
||||
if (str.length <= TableCell.MAX_CHARS_TO_SHOW) return str;
|
||||
|
||||
const { prefix, omitted, suffix } = TableCell.shortenString(str);
|
||||
return (
|
||||
|
@ -51,14 +52,8 @@ export class TableCell extends React.PureComponent<NullTableCellProps> {
|
|||
<span className="omitted">{omitted}</span>
|
||||
{suffix}
|
||||
<ActionIcon
|
||||
icon={IconNames.CLIPBOARD}
|
||||
onClick={() => {
|
||||
copy(str, { format: 'text/plain' });
|
||||
AppToaster.show({
|
||||
message: 'Value copied to clipboard',
|
||||
intent: Intent.SUCCESS,
|
||||
});
|
||||
}}
|
||||
icon={IconNames.MORE}
|
||||
onClick={() => (this.props.openModal ? this.props.openModal(str) : null)}
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
|
@ -67,7 +62,7 @@ export class TableCell extends React.PureComponent<NullTableCellProps> {
|
|||
static shortenString(str: string): ShortParts {
|
||||
// Print something like:
|
||||
// BAAAArAAEiQKpDAEAACwZCBAGSBgiSEAAAAQpAIDwAg...23 omitted...gwiRoQBJIC
|
||||
const omit = str.length - (TableCell.MAX_CHARS_TO_SHOW + 17);
|
||||
const omit = str.length - (TableCell.MAX_CHARS_TO_SHOW - 17);
|
||||
const prefix = str.substr(0, str.length - (omit + 10));
|
||||
const suffix = str.substr(str.length - 10);
|
||||
return {
|
||||
|
@ -89,9 +84,9 @@ export class TableCell extends React.PureComponent<NullTableCellProps> {
|
|||
</span>
|
||||
);
|
||||
} else if (Array.isArray(value)) {
|
||||
return TableCell.possiblyTruncate(`[${value.join(', ')}]`);
|
||||
return this.possiblyTruncate(`[${value.join(', ')}]`);
|
||||
} else {
|
||||
return TableCell.possiblyTruncate(String(value));
|
||||
return this.possiblyTruncate(String(value));
|
||||
}
|
||||
} else {
|
||||
if (timestamp) {
|
||||
|
|
|
@ -0,0 +1,107 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`clipboard dialog matches snapshot 1`] = `
|
||||
<div
|
||||
class="bp3-portal"
|
||||
>
|
||||
<div
|
||||
class="bp3-overlay bp3-overlay-open bp3-overlay-scroll-container"
|
||||
>
|
||||
<div
|
||||
class="bp3-overlay-backdrop bp3-overlay-appear bp3-overlay-appear-active"
|
||||
tabindex="0"
|
||||
/>
|
||||
<div
|
||||
class="bp3-dialog-container bp3-overlay-content bp3-overlay-appear bp3-overlay-appear-active"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="bp3-dialog show-value-dialog"
|
||||
>
|
||||
<div
|
||||
class="bp3-dialog-header"
|
||||
>
|
||||
<h4
|
||||
class="bp3-heading"
|
||||
>
|
||||
Show value
|
||||
</h4>
|
||||
<button
|
||||
aria-label="Close"
|
||||
class="bp3-button bp3-minimal bp3-dialog-close-button"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="bp3-icon bp3-icon-small-cross"
|
||||
icon="small-cross"
|
||||
>
|
||||
<svg
|
||||
data-icon="small-cross"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
width="20"
|
||||
>
|
||||
<desc>
|
||||
small-cross
|
||||
</desc>
|
||||
<path
|
||||
d="M11.41 10l3.29-3.29c.19-.18.3-.43.3-.71a1.003 1.003 0 0 0-1.71-.71L10 8.59l-3.29-3.3a1.003 1.003 0 0 0-1.42 1.42L8.59 10 5.3 13.29c-.19.18-.3.43-.3.71a1.003 1.003 0 0 0 1.71.71l3.29-3.3 3.29 3.29c.18.19.43.3.71.3a1.003 1.003 0 0 0 .71-1.71L11.41 10z"
|
||||
fill-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<textarea
|
||||
class="bp3-input"
|
||||
>
|
||||
Bot: Automatska zamjena teksta (-[[Administrativna podjela Meksika|Admin]] +[[Administrativna podjela Meksika|Admi]])
|
||||
</textarea>
|
||||
<div
|
||||
class="bp3-dialog-footer-actions"
|
||||
>
|
||||
<button
|
||||
class="bp3-button"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="bp3-icon bp3-icon-duplicate"
|
||||
icon="duplicate"
|
||||
>
|
||||
<svg
|
||||
data-icon="duplicate"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
width="16"
|
||||
>
|
||||
<desc>
|
||||
duplicate
|
||||
</desc>
|
||||
<path
|
||||
d="M15 0H5c-.55 0-1 .45-1 1v2h2V2h8v7h-1v2h2c.55 0 1-.45 1-1V1c0-.55-.45-1-1-1zm-4 4H1c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h10c.55 0 1-.45 1-1V5c0-.55-.45-1-1-1zm-1 10H2V6h8v8z"
|
||||
fill-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<span
|
||||
class="bp3-button-text"
|
||||
>
|
||||
Copy
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
class="bp3-button bp3-intent-primary"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="bp3-button-text"
|
||||
>
|
||||
Close
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
.show-value-dialog{
|
||||
&.bp3-dialog{
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.bp3-input{
|
||||
margin: 10px;
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
.bp3-dialog-footer-actions{
|
||||
padding-right: 10px;
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { render } from 'react-testing-library';
|
||||
|
||||
import { ShowValueDialog } from './show-value-dialog';
|
||||
|
||||
describe('clipboard dialog', () => {
|
||||
it('matches snapshot', () => {
|
||||
const compactionDialog = (
|
||||
<ShowValueDialog
|
||||
onClose={() => null}
|
||||
str={
|
||||
'Bot: Automatska zamjena teksta (-[[Administrativna podjela Meksika|Admin]] +[[Administrativna podjela Meksika|Admi]])'
|
||||
}
|
||||
/>
|
||||
);
|
||||
const { container } = render(compactionDialog, { container: document.body });
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { Button, Classes, Dialog, IconName, Intent, TextArea } from '@blueprintjs/core';
|
||||
import { IconNames } from '@blueprintjs/icons';
|
||||
import copy = require('copy-to-clipboard');
|
||||
import React from 'react';
|
||||
|
||||
import { AppToaster } from '../../singletons/toaster';
|
||||
|
||||
import './show-value-dialog.scss';
|
||||
|
||||
export interface ShowValueDialogProps extends React.Props<any> {
|
||||
onClose: () => void;
|
||||
str: string;
|
||||
}
|
||||
|
||||
export class ShowValueDialog extends React.PureComponent<ShowValueDialogProps> {
|
||||
constructor(props: ShowValueDialogProps) {
|
||||
super(props);
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
render() {
|
||||
const { onClose, str } = this.props;
|
||||
|
||||
return (
|
||||
<Dialog className="show-value-dialog" isOpen onClose={onClose} title={'Show value'}>
|
||||
<TextArea value={str} />
|
||||
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
|
||||
<Button
|
||||
icon={IconNames.DUPLICATE}
|
||||
text={'Copy'}
|
||||
onClick={() => {
|
||||
copy(str, { format: 'text/plain' });
|
||||
AppToaster.show({
|
||||
message: 'Value copied to clipboard',
|
||||
intent: Intent.SUCCESS,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<Button text={'Close'} intent={'primary'} onClick={onClose} />
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -25,6 +25,7 @@ import {
|
|||
Card,
|
||||
Classes,
|
||||
Code,
|
||||
Dialog,
|
||||
Elevation,
|
||||
FormGroup,
|
||||
H5,
|
||||
|
@ -50,6 +51,7 @@ import {
|
|||
Loader,
|
||||
} from '../../components';
|
||||
import { AsyncActionDialog } from '../../dialogs';
|
||||
import { ShowValueDialog } from '../../dialogs/show-value-dialog/show-value-dialog';
|
||||
import { AppToaster } from '../../singletons/toaster';
|
||||
import { UrlBaser } from '../../singletons/url-baser';
|
||||
import {
|
||||
|
@ -233,6 +235,8 @@ export interface LoadDataViewState {
|
|||
showResetConfirm: boolean;
|
||||
newRollup: boolean | null;
|
||||
newDimensionMode: DimensionMode | null;
|
||||
showViewValueModal: boolean;
|
||||
str: string;
|
||||
|
||||
// welcome
|
||||
overlordModules: string[] | null;
|
||||
|
@ -296,8 +300,10 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
|
|||
|
||||
// dialogs / modals
|
||||
showResetConfirm: false,
|
||||
showViewValueModal: false,
|
||||
newRollup: null,
|
||||
newDimensionMode: null,
|
||||
str: '',
|
||||
|
||||
// welcome
|
||||
overlordModules: null,
|
||||
|
@ -426,6 +432,7 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
|
|||
{step === 'loading' && this.renderLoading()}
|
||||
|
||||
{this.renderResetConfirm()}
|
||||
{this.renderViewValueModal()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -539,6 +546,14 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
|
|||
);
|
||||
}
|
||||
|
||||
renderViewValueModal() {
|
||||
const { showViewValueModal, str } = this.state;
|
||||
if (!showViewValueModal) return null;
|
||||
return (
|
||||
<ShowValueDialog onClose={() => this.setState({ showViewValueModal: false })} str={str} />
|
||||
);
|
||||
}
|
||||
|
||||
renderWelcomeStepMessage() {
|
||||
const { selectedComboType } = this.state;
|
||||
|
||||
|
@ -970,6 +985,7 @@ export class LoadDataView extends React.PureComponent<LoadDataViewProps, LoadDat
|
|||
)}
|
||||
</div>
|
||||
<ParseDataTable
|
||||
openModal={str => this.setState({ showViewValueModal: true, str: str })}
|
||||
sampleData={parserQueryState.data}
|
||||
columnFilter={columnFilter}
|
||||
canFlatten={canFlatten}
|
||||
|
|
|
@ -35,6 +35,7 @@ describe('parse data table', () => {
|
|||
|
||||
const parseDataTable = (
|
||||
<ParseDataTable
|
||||
openModal={() => null}
|
||||
sampleData={sampleData}
|
||||
columnFilter=""
|
||||
canFlatten={false}
|
||||
|
|
|
@ -34,6 +34,7 @@ export interface ParseDataTableProps extends React.Props<any> {
|
|||
flattenedColumnsOnly: boolean;
|
||||
flattenFields: FlattenField[];
|
||||
onFlattenFieldSelect: (field: FlattenField, index: number) => void;
|
||||
openModal: (str: string) => void;
|
||||
}
|
||||
|
||||
export class ParseDataTable extends React.PureComponent<ParseDataTableProps> {
|
||||
|
@ -77,7 +78,7 @@ export class ParseDataTable extends React.PureComponent<ParseDataTableProps> {
|
|||
if (row.original.unparseable) {
|
||||
return <TableCell unparseable />;
|
||||
}
|
||||
return <TableCell value={row.value} />;
|
||||
return <TableCell value={row.value} openModal={str => this.props.openModal(str)} />;
|
||||
},
|
||||
headerClassName: classNames({
|
||||
flattened: flattenField,
|
||||
|
|
Loading…
Reference in New Issue