Merge pull request #3575 from influxdata/ifql/copy-paste-schema
flux/Copy to clipboard in schema explorerpull/3590/head
commit
4cc0c93fd2
|
@ -140,6 +140,7 @@
|
|||
"react": "^16.3.1",
|
||||
"react-addons-shallow-compare": "^15.0.2",
|
||||
"react-codemirror2": "^4.2.1",
|
||||
"react-copy-to-clipboard": "^5.0.1",
|
||||
"react-custom-scrollbars": "^4.1.1",
|
||||
"react-dimensions": "^1.2.0",
|
||||
"react-dnd": "^2.6.0",
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import React, {PureComponent} from 'react'
|
||||
|
||||
import {NotificationContext} from 'src/flux/containers/CheckServices'
|
||||
import DatabaseListItem from 'src/flux/components/DatabaseListItem'
|
||||
|
||||
import {showDatabases} from 'src/shared/apis/metaQuery'
|
||||
|
@ -49,7 +50,13 @@ class DatabaseList extends PureComponent<Props, State> {
|
|||
const {service} = this.props
|
||||
|
||||
return databases.map(db => {
|
||||
return <DatabaseListItem db={db} key={db} service={service} />
|
||||
return (
|
||||
<NotificationContext.Consumer key={db}>
|
||||
{({notify}) => (
|
||||
<DatabaseListItem db={db} service={service} notify={notify} />
|
||||
)}
|
||||
</NotificationContext.Consumer>
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,21 @@
|
|||
import React, {PureComponent, ChangeEvent, MouseEvent} from 'react'
|
||||
import classnames from 'classnames'
|
||||
import {CopyToClipboard} from 'react-copy-to-clipboard'
|
||||
|
||||
import {tagKeys as fetchTagKeys} from 'src/shared/apis/flux/metaQueries'
|
||||
import parseValuesColumn from 'src/shared/parsing/flux/values'
|
||||
import TagList from 'src/flux/components/TagList'
|
||||
import {Service} from 'src/types'
|
||||
import {
|
||||
notifyCopyToClipboardSuccess,
|
||||
notifyCopyToClipboardFailed,
|
||||
} from 'src/shared/copy/notifications'
|
||||
|
||||
import {Service, NotificationAction} from 'src/types'
|
||||
|
||||
interface Props {
|
||||
db: string
|
||||
service: Service
|
||||
notify: NotificationAction
|
||||
}
|
||||
|
||||
interface State {
|
||||
|
@ -40,33 +47,24 @@ class DatabaseListItem extends PureComponent<Props, State> {
|
|||
}
|
||||
|
||||
public render() {
|
||||
const {db, service} = this.props
|
||||
const {searchTerm} = this.state
|
||||
const {db} = this.props
|
||||
|
||||
return (
|
||||
<div className={this.className} onClick={this.handleClick}>
|
||||
<div className="flux-schema-item">
|
||||
<div className="flux-schema-item-toggle" />
|
||||
{db}
|
||||
<span className="flux-schema-type">Bucket</span>
|
||||
</div>
|
||||
{this.state.isOpen && (
|
||||
<>
|
||||
<div className="flux-schema--filter">
|
||||
<input
|
||||
className="form-control input-sm"
|
||||
placeholder={`Filter within ${db}`}
|
||||
type="text"
|
||||
spellCheck={false}
|
||||
autoComplete="off"
|
||||
value={searchTerm}
|
||||
onClick={this.handleInputClick}
|
||||
onChange={this.onSearch}
|
||||
/>
|
||||
<div className="flex-schema-item-group">
|
||||
<div className="flux-schema-item-toggle" />
|
||||
{db}
|
||||
<span className="flux-schema-type">Bucket</span>
|
||||
</div>
|
||||
<CopyToClipboard text={db} onCopy={this.handleCopyAttempt}>
|
||||
<div className="flux-schema-copy" onClick={this.handleClickCopy}>
|
||||
<span className="icon duplicate" title="copy to clipboard" />
|
||||
Copy
|
||||
</div>
|
||||
<TagList db={db} service={service} tags={this.tags} filter={[]} />
|
||||
</>
|
||||
)}
|
||||
</CopyToClipboard>
|
||||
</div>
|
||||
{this.filterAndTagList}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -83,6 +81,47 @@ class DatabaseListItem extends PureComponent<Props, State> {
|
|||
})
|
||||
}
|
||||
|
||||
private get filterAndTagList(): JSX.Element {
|
||||
const {db, service} = this.props
|
||||
const {isOpen, searchTerm} = this.state
|
||||
|
||||
if (isOpen) {
|
||||
return (
|
||||
<>
|
||||
<div className="flux-schema--filter">
|
||||
<input
|
||||
className="form-control input-sm"
|
||||
placeholder={`Filter within ${db}`}
|
||||
type="text"
|
||||
spellCheck={false}
|
||||
autoComplete="off"
|
||||
value={searchTerm}
|
||||
onClick={this.handleInputClick}
|
||||
onChange={this.onSearch}
|
||||
/>
|
||||
</div>
|
||||
<TagList db={db} service={service} tags={this.tags} filter={[]} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private handleClickCopy = e => {
|
||||
e.stopPropagation()
|
||||
}
|
||||
|
||||
private handleCopyAttempt = (
|
||||
copiedText: string,
|
||||
isSuccessful: boolean
|
||||
): void => {
|
||||
const {notify} = this.props
|
||||
if (isSuccessful) {
|
||||
notify(notifyCopyToClipboardSuccess(copiedText))
|
||||
} else {
|
||||
notify(notifyCopyToClipboardFailed(copiedText))
|
||||
}
|
||||
}
|
||||
|
||||
private onSearch = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({
|
||||
searchTerm: e.target.value,
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import React, {PureComponent, MouseEvent} from 'react'
|
||||
|
||||
import {SchemaFilter, Service} from 'src/types'
|
||||
import TagListItem from 'src/flux/components/TagListItem'
|
||||
import {NotificationContext} from 'src/flux/containers/CheckServices'
|
||||
|
||||
import {SchemaFilter, Service} from 'src/types'
|
||||
|
||||
interface Props {
|
||||
db: string
|
||||
|
@ -28,13 +30,17 @@ export default class TagList extends PureComponent<Props, State> {
|
|||
return (
|
||||
<>
|
||||
{tags.map(t => (
|
||||
<TagListItem
|
||||
key={t}
|
||||
db={db}
|
||||
tagKey={t}
|
||||
service={service}
|
||||
filter={filter}
|
||||
/>
|
||||
<NotificationContext.Consumer key={t}>
|
||||
{({notify}) => (
|
||||
<TagListItem
|
||||
db={db}
|
||||
tagKey={t}
|
||||
service={service}
|
||||
filter={filter}
|
||||
notify={notify}
|
||||
/>
|
||||
)}
|
||||
</NotificationContext.Consumer>
|
||||
))}
|
||||
</>
|
||||
)
|
||||
|
|
|
@ -6,6 +6,7 @@ import React, {
|
|||
} from 'react'
|
||||
|
||||
import _ from 'lodash'
|
||||
import {CopyToClipboard} from 'react-copy-to-clipboard'
|
||||
|
||||
import {Service, SchemaFilter, RemoteDataState} from 'src/types'
|
||||
import {tagValues as fetchTagValues} from 'src/shared/apis/flux/metaQueries'
|
||||
|
@ -14,12 +15,19 @@ import parseValuesColumn from 'src/shared/parsing/flux/values'
|
|||
import TagValueList from 'src/flux/components/TagValueList'
|
||||
import LoaderSkeleton from 'src/flux/components/LoaderSkeleton'
|
||||
import LoadingSpinner from 'src/flux/components/LoadingSpinner'
|
||||
import {
|
||||
notifyCopyToClipboardSuccess,
|
||||
notifyCopyToClipboardFailed,
|
||||
} from 'src/shared/copy/notifications'
|
||||
|
||||
import {NotificationAction} from 'src/types'
|
||||
|
||||
interface Props {
|
||||
tagKey: string
|
||||
db: string
|
||||
service: Service
|
||||
filter: SchemaFilter[]
|
||||
notify: NotificationAction
|
||||
}
|
||||
|
||||
interface State {
|
||||
|
@ -61,9 +69,17 @@ export default class TagListItem extends PureComponent<Props, State> {
|
|||
return (
|
||||
<div className={this.className}>
|
||||
<div className="flux-schema-item" onClick={this.handleClick}>
|
||||
<div className="flux-schema-item-toggle" />
|
||||
{tagKey}
|
||||
<span className="flux-schema-type">Tag Key</span>
|
||||
<div className="flex-schema-item-group">
|
||||
<div className="flux-schema-item-toggle" />
|
||||
{tagKey}
|
||||
<span className="flux-schema-type">Tag Key</span>{' '}
|
||||
</div>
|
||||
<CopyToClipboard text={tagKey} onCopy={this.handleCopyAttempt}>
|
||||
<div className="flux-schema-copy" onClick={this.handleClickCopy}>
|
||||
<span className="icon duplicate" title="copy to clipboard" />
|
||||
Copy
|
||||
</div>
|
||||
</CopyToClipboard>
|
||||
</div>
|
||||
{this.state.isOpen && (
|
||||
<>
|
||||
|
@ -220,6 +236,22 @@ export default class TagListItem extends PureComponent<Props, State> {
|
|||
)
|
||||
}
|
||||
|
||||
private handleClickCopy = e => {
|
||||
e.stopPropagation()
|
||||
}
|
||||
|
||||
private handleCopyAttempt = (
|
||||
copiedText: string,
|
||||
isSuccessful: boolean
|
||||
): void => {
|
||||
const {notify} = this.props
|
||||
if (isSuccessful) {
|
||||
notify(notifyCopyToClipboardSuccess(copiedText))
|
||||
} else {
|
||||
notify(notifyCopyToClipboardFailed(copiedText))
|
||||
}
|
||||
}
|
||||
|
||||
private async getCount() {
|
||||
const {service, db, filter, tagKey} = this.props
|
||||
const {limit, searchTerm} = this.state
|
||||
|
|
|
@ -2,6 +2,8 @@ import React, {PureComponent, MouseEvent} from 'react'
|
|||
|
||||
import TagValueListItem from 'src/flux/components/TagValueListItem'
|
||||
import LoadingSpinner from 'src/flux/components/LoadingSpinner'
|
||||
import {NotificationContext} from 'src/flux/containers/CheckServices'
|
||||
|
||||
import {Service, SchemaFilter} from 'src/types'
|
||||
|
||||
interface Props {
|
||||
|
@ -30,14 +32,19 @@ export default class TagValueList extends PureComponent<Props> {
|
|||
return (
|
||||
<>
|
||||
{values.map((v, i) => (
|
||||
<TagValueListItem
|
||||
key={i}
|
||||
db={db}
|
||||
value={v}
|
||||
tagKey={tagKey}
|
||||
service={service}
|
||||
filter={filter}
|
||||
/>
|
||||
<NotificationContext.Consumer key={v}>
|
||||
{({notify}) => (
|
||||
<TagValueListItem
|
||||
key={i}
|
||||
db={db}
|
||||
value={v}
|
||||
tagKey={tagKey}
|
||||
service={service}
|
||||
filter={filter}
|
||||
notify={notify}
|
||||
/>
|
||||
)}
|
||||
</NotificationContext.Consumer>
|
||||
))}
|
||||
{shouldShowMoreValues && (
|
||||
<div className="flux-schema-tree flux-tree-node">
|
||||
|
|
|
@ -1,10 +1,23 @@
|
|||
import React, {PureComponent, MouseEvent, ChangeEvent} from 'react'
|
||||
|
||||
import {CopyToClipboard} from 'react-copy-to-clipboard'
|
||||
|
||||
import {tagKeys as fetchTagKeys} from 'src/shared/apis/flux/metaQueries'
|
||||
import parseValuesColumn from 'src/shared/parsing/flux/values'
|
||||
import TagList from 'src/flux/components/TagList'
|
||||
import LoaderSkeleton from 'src/flux/components/LoaderSkeleton'
|
||||
import {Service, SchemaFilter, RemoteDataState} from 'src/types'
|
||||
|
||||
import {
|
||||
notifyCopyToClipboardSuccess,
|
||||
notifyCopyToClipboardFailed,
|
||||
} from 'src/shared/copy/notifications'
|
||||
|
||||
import {
|
||||
Service,
|
||||
SchemaFilter,
|
||||
RemoteDataState,
|
||||
NotificationAction,
|
||||
} from 'src/types'
|
||||
|
||||
interface Props {
|
||||
db: string
|
||||
|
@ -12,6 +25,7 @@ interface Props {
|
|||
tagKey: string
|
||||
value: string
|
||||
filter: SchemaFilter[]
|
||||
notify: NotificationAction
|
||||
}
|
||||
|
||||
interface State {
|
||||
|
@ -39,9 +53,17 @@ class TagValueListItem extends PureComponent<Props, State> {
|
|||
return (
|
||||
<div className={this.className} onClick={this.handleClick}>
|
||||
<div className="flux-schema-item">
|
||||
<div className="flux-schema-item-toggle" />
|
||||
{value}
|
||||
<span className="flux-schema-type">Tag Value</span>
|
||||
<div className="flex-schema-item-group">
|
||||
<div className="flux-schema-item-toggle" />
|
||||
{value}
|
||||
<span className="flux-schema-type">Tag Value</span>
|
||||
</div>
|
||||
<CopyToClipboard text={value} onCopy={this.handleCopyAttempt}>
|
||||
<div className="flux-schema-copy" onClick={this.handleClickCopy}>
|
||||
<span className="icon duplicate" title="copy to clipboard" />
|
||||
Copy
|
||||
</div>
|
||||
</CopyToClipboard>
|
||||
</div>
|
||||
{this.state.isOpen && (
|
||||
<>
|
||||
|
@ -127,6 +149,22 @@ class TagValueListItem extends PureComponent<Props, State> {
|
|||
this.setState({isOpen: !this.state.isOpen})
|
||||
}
|
||||
|
||||
private handleClickCopy = e => {
|
||||
e.stopPropagation()
|
||||
}
|
||||
|
||||
private handleCopyAttempt = (
|
||||
copiedText: string,
|
||||
isSuccessful: boolean
|
||||
): void => {
|
||||
const {notify} = this.props
|
||||
if (isSuccessful) {
|
||||
notify(notifyCopyToClipboardSuccess(copiedText))
|
||||
} else {
|
||||
notify(notifyCopyToClipboardFailed(copiedText))
|
||||
}
|
||||
}
|
||||
|
||||
private onSearch = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({
|
||||
searchTerm: e.target.value,
|
||||
|
|
|
@ -15,6 +15,8 @@ import {
|
|||
import * as a from 'src/shared/actions/overlayTechnology'
|
||||
import * as b from 'src/shared/actions/services'
|
||||
|
||||
export const NotificationContext = React.createContext()
|
||||
|
||||
const actions = {...a, ...b}
|
||||
|
||||
interface Props {
|
||||
|
@ -54,14 +56,16 @@ export class CheckServices extends PureComponent<Props & WithRouterProps> {
|
|||
}
|
||||
|
||||
return (
|
||||
<FluxPage
|
||||
source={this.source}
|
||||
services={services}
|
||||
links={links}
|
||||
script={script}
|
||||
notify={notify}
|
||||
updateScript={updateScript}
|
||||
/>
|
||||
<NotificationContext.Provider value={{notify}}>
|
||||
<FluxPage
|
||||
source={this.source}
|
||||
services={services}
|
||||
links={links}
|
||||
script={script}
|
||||
notify={notify}
|
||||
updateScript={updateScript}
|
||||
/>
|
||||
</NotificationContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -639,6 +639,17 @@ export const analyzeSuccess = {
|
|||
message: 'No errors found. Happy Happy Joy Joy!',
|
||||
}
|
||||
|
||||
export const notifyCopyToClipboardSuccess = text => ({
|
||||
...defaultSuccessNotification,
|
||||
icon: 'dash-h',
|
||||
message: `'${text}' has been copied to clipboard.`,
|
||||
})
|
||||
|
||||
export const notifyCopyToClipboardFailed = text => ({
|
||||
...defaultErrorNotification,
|
||||
message: `'${text}' was not copied to clipboard.`,
|
||||
})
|
||||
|
||||
// Service notifications
|
||||
export const couldNotGetServices = {
|
||||
...defaultErrorNotification,
|
||||
|
|
|
@ -18,7 +18,7 @@ $flux-tree-line: 2px;
|
|||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
padding-left: 0;
|
||||
>.flux-schema-tree {
|
||||
> .flux-schema-tree {
|
||||
padding-left: $flux-tree-indent;
|
||||
}
|
||||
}
|
||||
|
@ -68,7 +68,7 @@ $flux-tree-line: 2px;
|
|||
color: $g11-sidewalk;
|
||||
white-space: nowrap;
|
||||
transition: color 0.25s ease, background-color 0.25s ease;
|
||||
>span.icon {
|
||||
> span.icon {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: $flux-tree-indent / 2;
|
||||
|
@ -83,7 +83,7 @@ $flux-tree-line: 2px;
|
|||
background-color: $g17-whisper;
|
||||
}
|
||||
}
|
||||
.expanded>& {
|
||||
.expanded > & {
|
||||
color: $c-pool;
|
||||
.flux-schema-item-toggle:before,
|
||||
.flux-schema-item-toggle:after {
|
||||
|
@ -120,17 +120,28 @@ $flux-tree-line: 2px;
|
|||
}
|
||||
}
|
||||
|
||||
.flex-schema-item-group {
|
||||
display: flex;
|
||||
flex: 1 0 0;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@keyframes skeleton-animation {
|
||||
0% {
|
||||
background-position: 100% 50%
|
||||
background-position: 100% 50%;
|
||||
}
|
||||
100% {
|
||||
background-position: 0% 50%
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.flux-schema-item-skeleton {
|
||||
background: linear-gradient(70deg, $g4-onyx 0%, $g5-pepper 50%, $g4-onyx 100%);
|
||||
background: linear-gradient(
|
||||
70deg,
|
||||
$g4-onyx 0%,
|
||||
$g5-pepper 50%,
|
||||
$g4-onyx 100%
|
||||
);
|
||||
border-radius: 4px;
|
||||
height: 60%;
|
||||
background-size: 400% 400%;
|
||||
|
@ -196,7 +207,24 @@ $flux-tree-line: 2px;
|
|||
}
|
||||
}
|
||||
|
||||
.flux-schema-tree>.flux-schema--filter {
|
||||
.flux-schema-copy {
|
||||
color: $g11-sidewalk;
|
||||
display: inline-block;
|
||||
opacity: 0;
|
||||
transition: opacity 0.25s ease;
|
||||
margin-left: 8px;
|
||||
.flux-schema-item:hover & {
|
||||
opacity: 1;
|
||||
}
|
||||
> span.icon {
|
||||
margin-right: 3px;
|
||||
}
|
||||
&:hover {
|
||||
color: $g15-platinum;
|
||||
}
|
||||
}
|
||||
|
||||
.flux-schema-tree > .flux-schema--filter {
|
||||
margin-left: $flux-tree-indent / 2;
|
||||
margin-right: $flux-tree-indent / 2;
|
||||
margin-top: 1px;
|
||||
|
@ -204,12 +232,12 @@ $flux-tree-line: 2px;
|
|||
max-width: 220px;
|
||||
}
|
||||
|
||||
.flux-schema--filter>input.input-sm {
|
||||
.flux-schema--filter > input.input-sm {
|
||||
height: 25px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.tag-value-list--header>.flux-schema--filter {
|
||||
.tag-value-list--header > .flux-schema--filter {
|
||||
display: inline-block;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
@ -232,7 +260,7 @@ $flux-tree-line: 2px;
|
|||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.loading-spinner .spinner>div {
|
||||
.loading-spinner .spinner > div {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background-color: $g8-storm;
|
||||
|
@ -253,10 +281,10 @@ $flux-tree-line: 2px;
|
|||
0%,
|
||||
80%,
|
||||
100% {
|
||||
-webkit-transform: scale(0)
|
||||
-webkit-transform: scale(0);
|
||||
}
|
||||
40% {
|
||||
-webkit-transform: scale(1.0)
|
||||
-webkit-transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -268,7 +296,7 @@ $flux-tree-line: 2px;
|
|||
transform: scale(0);
|
||||
}
|
||||
40% {
|
||||
-webkit-transform: scale(1.0);
|
||||
transform: scale(1.0);
|
||||
-webkit-transform: scale(1);
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,11 @@ import {
|
|||
import {AlertRule, Kapacitor, Task} from './kapacitor'
|
||||
import {Source, SourceLinks} from './sources'
|
||||
import {DropdownAction, DropdownItem} from './shared'
|
||||
import {Notification, NotificationFunc} from './notifications'
|
||||
import {
|
||||
Notification,
|
||||
NotificationFunc,
|
||||
NotificationAction,
|
||||
} from './notifications'
|
||||
import {FluxTable, ScriptStatus, SchemaFilter, RemoteDataState} from './flux'
|
||||
|
||||
export {
|
||||
|
@ -57,6 +61,7 @@ export {
|
|||
Task,
|
||||
Notification,
|
||||
NotificationFunc,
|
||||
NotificationAction,
|
||||
Axes,
|
||||
Dashboard,
|
||||
Service,
|
||||
|
|
|
@ -7,3 +7,5 @@ export interface Notification {
|
|||
}
|
||||
|
||||
export type NotificationFunc = (message: any) => Notification
|
||||
|
||||
export type NotificationAction = (message: Notification) => void
|
||||
|
|
Loading…
Reference in New Issue