feat(ui): expose bucketid (#18319)
* refactor: extract bucket card actions and meta into separate components * feat: allow bucket ID to be copied * chore: cleanup * chore: update changelog * feat: add copiable ID to demo data bucket cardspull/18377/head
parent
17791301ab
commit
5dfeb0ad82
|
@ -14,6 +14,10 @@
|
|||
1. [18335](https://github.com/influxdata/influxdb/pull/18335): Disable failing when providing an unexpected error to influx CLI
|
||||
1. [18345](https://github.com/influxdata/influxdb/pull/18345): Have influx delete cmd respect the config
|
||||
|
||||
### UI Improvements
|
||||
|
||||
1. [18319](https://github.com/influxdata/influxdb/pull/18319): Display bucket ID in bucket list and enable 1 click copying
|
||||
|
||||
## v2.0.0-beta.11 [2020-05-26]
|
||||
|
||||
### Features
|
||||
|
|
|
@ -1,40 +1,19 @@
|
|||
// Libraries
|
||||
import React, {FC} from 'react'
|
||||
import {withRouter, WithRouterProps} from 'react-router'
|
||||
import {connect} from 'react-redux'
|
||||
import _ from 'lodash'
|
||||
|
||||
// Components
|
||||
import {
|
||||
Button,
|
||||
ResourceCard,
|
||||
FlexBox,
|
||||
FlexDirection,
|
||||
ComponentSize,
|
||||
} from '@influxdata/clockface'
|
||||
import {ResourceCard} from '@influxdata/clockface'
|
||||
import BucketContextMenu from 'src/buckets/components/BucketContextMenu'
|
||||
import BucketAddDataButton from 'src/buckets/components/BucketAddDataButton'
|
||||
import InlineLabels from 'src/shared/components/inlineLabels/InlineLabels'
|
||||
import {FeatureFlag} from 'src/shared/utils/featureFlag'
|
||||
import BucketCardMeta from 'src/buckets/components/BucketCardMeta'
|
||||
import BucketCardActions from 'src/buckets/components/BucketCardActions'
|
||||
|
||||
// Constants
|
||||
import {isSystemBucket} from 'src/buckets/constants/index'
|
||||
|
||||
// Actions
|
||||
import {addBucketLabel, deleteBucketLabel} from 'src/buckets/actions/thunks'
|
||||
import {setBucketInfo} from 'src/dataLoaders/actions/steps'
|
||||
import {setDataLoadersType} from 'src/dataLoaders/actions/dataLoaders'
|
||||
|
||||
// Types
|
||||
import {Label, OwnBucket} from 'src/types'
|
||||
import {DataLoaderType} from 'src/types/dataLoaders'
|
||||
|
||||
interface DispatchProps {
|
||||
onAddBucketLabel: typeof addBucketLabel
|
||||
onDeleteBucketLabel: typeof deleteBucketLabel
|
||||
onSetDataLoadersBucket: typeof setBucketInfo
|
||||
onSetDataLoadersType: typeof setDataLoadersType
|
||||
}
|
||||
import {OwnBucket} from 'src/types'
|
||||
|
||||
interface Props {
|
||||
bucket: OwnBucket
|
||||
|
@ -44,123 +23,18 @@ interface Props {
|
|||
onFilterChange: (searchTerm: string) => void
|
||||
}
|
||||
|
||||
const BucketCard: FC<Props & WithRouterProps & DispatchProps> = ({
|
||||
const BucketCard: FC<Props & WithRouterProps> = ({
|
||||
bucket,
|
||||
onDeleteBucket,
|
||||
onFilterChange,
|
||||
onAddBucketLabel,
|
||||
onDeleteBucketLabel,
|
||||
onDeleteData,
|
||||
router,
|
||||
params: {orgID},
|
||||
onSetDataLoadersBucket,
|
||||
onSetDataLoadersType,
|
||||
}) => {
|
||||
const handleAddLabel = (label: Label) => {
|
||||
onAddBucketLabel(bucket.id, label)
|
||||
}
|
||||
|
||||
const handleRemoveLabel = (label: Label) => {
|
||||
onDeleteBucketLabel(bucket.id, label)
|
||||
}
|
||||
|
||||
const handleClickSettings = () => {
|
||||
router.push(`/orgs/${orgID}/load-data/buckets/${bucket.id}/edit`)
|
||||
}
|
||||
|
||||
const handleNameClick = () => {
|
||||
router.push(`/orgs/${orgID}/data-explorer?bucket=${bucket.name}`)
|
||||
}
|
||||
|
||||
const handleAddCollector = () => {
|
||||
onSetDataLoadersBucket(orgID, bucket.name, bucket.id)
|
||||
|
||||
onSetDataLoadersType(DataLoaderType.Streaming)
|
||||
router.push(`/orgs/${orgID}/load-data/buckets/${bucket.id}/telegrafs/new`)
|
||||
}
|
||||
|
||||
const handleAddLineProtocol = () => {
|
||||
onSetDataLoadersBucket(orgID, bucket.name, bucket.id)
|
||||
|
||||
onSetDataLoadersType(DataLoaderType.LineProtocol)
|
||||
router.push(
|
||||
`/orgs/${orgID}/load-data/buckets/${bucket.id}/line-protocols/new`
|
||||
)
|
||||
}
|
||||
|
||||
const handleAddClientLibrary = (): void => {
|
||||
onSetDataLoadersBucket(orgID, bucket.name, bucket.id)
|
||||
onSetDataLoadersType(DataLoaderType.ClientLibrary)
|
||||
|
||||
router.push(`/orgs/${orgID}/load-data/client-libraries`)
|
||||
}
|
||||
|
||||
const handleAddScraper = () => {
|
||||
onSetDataLoadersBucket(orgID, bucket.name, bucket.id)
|
||||
|
||||
onSetDataLoadersType(DataLoaderType.Scraping)
|
||||
router.push(`/orgs/${orgID}/load-data/buckets/${bucket.id}/scrapers/new`)
|
||||
}
|
||||
|
||||
const actionButtons = (
|
||||
<FlexBox
|
||||
direction={FlexDirection.Row}
|
||||
margin={ComponentSize.Small}
|
||||
style={{marginTop: '4px'}}
|
||||
>
|
||||
<InlineLabels
|
||||
selectedLabelIDs={bucket.labels}
|
||||
onFilterChange={onFilterChange}
|
||||
onAddLabel={handleAddLabel}
|
||||
onRemoveLabel={handleRemoveLabel}
|
||||
/>
|
||||
<BucketAddDataButton
|
||||
onAddCollector={handleAddCollector}
|
||||
onAddLineProtocol={handleAddLineProtocol}
|
||||
onAddClientLibrary={handleAddClientLibrary}
|
||||
onAddScraper={handleAddScraper}
|
||||
/>
|
||||
<Button
|
||||
text="Settings"
|
||||
testID="bucket-settings"
|
||||
size={ComponentSize.ExtraSmall}
|
||||
onClick={handleClickSettings}
|
||||
/>
|
||||
<FeatureFlag name="deleteWithPredicate">
|
||||
<Button
|
||||
text="Delete Data By Filter"
|
||||
testID="bucket-delete-bucket"
|
||||
size={ComponentSize.ExtraSmall}
|
||||
onClick={() => onDeleteData(bucket)}
|
||||
/>
|
||||
</FeatureFlag>
|
||||
</FlexBox>
|
||||
)
|
||||
|
||||
let cardMeta = (
|
||||
<ResourceCard.Meta>
|
||||
<span data-testid="bucket-retention">
|
||||
Retention: {_.capitalize(bucket.readableRetention)}
|
||||
</span>
|
||||
</ResourceCard.Meta>
|
||||
)
|
||||
|
||||
if (bucket.type !== 'user') {
|
||||
cardMeta = (
|
||||
<ResourceCard.Meta>
|
||||
<span
|
||||
className="system-bucket"
|
||||
key={`system-bucket-indicator-${bucket.id}`}
|
||||
>
|
||||
System Bucket
|
||||
</span>
|
||||
<span data-testid="bucket-retention">
|
||||
Retention: {_.capitalize(bucket.readableRetention)}
|
||||
</span>
|
||||
</ResourceCard.Meta>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<ResourceCard
|
||||
testID={`bucket-card ${bucket.name}`}
|
||||
|
@ -175,20 +49,16 @@ const BucketCard: FC<Props & WithRouterProps & DispatchProps> = ({
|
|||
onClick={handleNameClick}
|
||||
name={bucket.name}
|
||||
/>
|
||||
{cardMeta}
|
||||
{bucket.type === 'user' && actionButtons}
|
||||
<BucketCardMeta bucket={bucket} />
|
||||
<BucketCardActions
|
||||
bucket={bucket}
|
||||
orgID={orgID}
|
||||
bucketType={bucket.type}
|
||||
onDeleteData={onDeleteData}
|
||||
onFilterChange={onFilterChange}
|
||||
/>
|
||||
</ResourceCard>
|
||||
)
|
||||
}
|
||||
|
||||
const mdtp: DispatchProps = {
|
||||
onAddBucketLabel: addBucketLabel,
|
||||
onDeleteBucketLabel: deleteBucketLabel,
|
||||
onSetDataLoadersBucket: setBucketInfo,
|
||||
onSetDataLoadersType: setDataLoadersType,
|
||||
}
|
||||
|
||||
export default connect<{}, DispatchProps>(
|
||||
null,
|
||||
mdtp
|
||||
)(withRouter<Props>(BucketCard))
|
||||
export default withRouter<Props>(BucketCard)
|
||||
|
|
|
@ -0,0 +1,146 @@
|
|||
// Libraries
|
||||
import React, {FC} from 'react'
|
||||
import {withRouter, WithRouterProps} from 'react-router'
|
||||
import {connect} from 'react-redux'
|
||||
import _ from 'lodash'
|
||||
|
||||
// Components
|
||||
import {
|
||||
Button,
|
||||
FlexBox,
|
||||
FlexDirection,
|
||||
ComponentSize,
|
||||
} from '@influxdata/clockface'
|
||||
import BucketAddDataButton from 'src/buckets/components/BucketAddDataButton'
|
||||
import InlineLabels from 'src/shared/components/inlineLabels/InlineLabels'
|
||||
import {FeatureFlag} from 'src/shared/utils/featureFlag'
|
||||
|
||||
// Actions
|
||||
import {addBucketLabel, deleteBucketLabel} from 'src/buckets/actions/thunks'
|
||||
import {setBucketInfo} from 'src/dataLoaders/actions/steps'
|
||||
import {setDataLoadersType} from 'src/dataLoaders/actions/dataLoaders'
|
||||
|
||||
// Types
|
||||
import {Label, OwnBucket} from 'src/types'
|
||||
import {DataLoaderType} from 'src/types/dataLoaders'
|
||||
|
||||
interface DispatchProps {
|
||||
onAddBucketLabel: typeof addBucketLabel
|
||||
onDeleteBucketLabel: typeof deleteBucketLabel
|
||||
onSetDataLoadersBucket: typeof setBucketInfo
|
||||
onSetDataLoadersType: typeof setDataLoadersType
|
||||
}
|
||||
|
||||
interface Props {
|
||||
bucket: OwnBucket
|
||||
bucketType: 'user' | 'system'
|
||||
orgID: string
|
||||
onDeleteData: (b: OwnBucket) => void
|
||||
onFilterChange: (searchTerm: string) => void
|
||||
}
|
||||
|
||||
const BucketCardActions: FC<Props & WithRouterProps & DispatchProps> = ({
|
||||
bucket,
|
||||
bucketType,
|
||||
orgID,
|
||||
onFilterChange,
|
||||
onAddBucketLabel,
|
||||
onDeleteBucketLabel,
|
||||
onDeleteData,
|
||||
router,
|
||||
onSetDataLoadersBucket,
|
||||
onSetDataLoadersType,
|
||||
}) => {
|
||||
if (bucketType === 'system') {
|
||||
return null
|
||||
}
|
||||
|
||||
const handleAddLabel = (label: Label) => {
|
||||
onAddBucketLabel(bucket.id, label)
|
||||
}
|
||||
|
||||
const handleRemoveLabel = (label: Label) => {
|
||||
onDeleteBucketLabel(bucket.id, label)
|
||||
}
|
||||
|
||||
const handleClickSettings = () => {
|
||||
router.push(`/orgs/${orgID}/load-data/buckets/${bucket.id}/edit`)
|
||||
}
|
||||
|
||||
const handleAddCollector = () => {
|
||||
onSetDataLoadersBucket(orgID, bucket.name, bucket.id)
|
||||
|
||||
onSetDataLoadersType(DataLoaderType.Streaming)
|
||||
router.push(`/orgs/${orgID}/load-data/buckets/${bucket.id}/telegrafs/new`)
|
||||
}
|
||||
|
||||
const handleAddLineProtocol = () => {
|
||||
onSetDataLoadersBucket(orgID, bucket.name, bucket.id)
|
||||
|
||||
onSetDataLoadersType(DataLoaderType.LineProtocol)
|
||||
router.push(
|
||||
`/orgs/${orgID}/load-data/buckets/${bucket.id}/line-protocols/new`
|
||||
)
|
||||
}
|
||||
|
||||
const handleAddClientLibrary = (): void => {
|
||||
onSetDataLoadersBucket(orgID, bucket.name, bucket.id)
|
||||
onSetDataLoadersType(DataLoaderType.ClientLibrary)
|
||||
|
||||
router.push(`/orgs/${orgID}/load-data/client-libraries`)
|
||||
}
|
||||
|
||||
const handleAddScraper = () => {
|
||||
onSetDataLoadersBucket(orgID, bucket.name, bucket.id)
|
||||
|
||||
onSetDataLoadersType(DataLoaderType.Scraping)
|
||||
router.push(`/orgs/${orgID}/load-data/buckets/${bucket.id}/scrapers/new`)
|
||||
}
|
||||
|
||||
return (
|
||||
<FlexBox
|
||||
direction={FlexDirection.Row}
|
||||
margin={ComponentSize.Small}
|
||||
style={{marginTop: '4px'}}
|
||||
>
|
||||
<InlineLabels
|
||||
selectedLabelIDs={bucket.labels}
|
||||
onFilterChange={onFilterChange}
|
||||
onAddLabel={handleAddLabel}
|
||||
onRemoveLabel={handleRemoveLabel}
|
||||
/>
|
||||
<BucketAddDataButton
|
||||
onAddCollector={handleAddCollector}
|
||||
onAddLineProtocol={handleAddLineProtocol}
|
||||
onAddClientLibrary={handleAddClientLibrary}
|
||||
onAddScraper={handleAddScraper}
|
||||
/>
|
||||
<Button
|
||||
text="Settings"
|
||||
testID="bucket-settings"
|
||||
size={ComponentSize.ExtraSmall}
|
||||
onClick={handleClickSettings}
|
||||
/>
|
||||
<FeatureFlag name="deleteWithPredicate">
|
||||
<Button
|
||||
text="Delete Data By Filter"
|
||||
testID="bucket-delete-bucket"
|
||||
size={ComponentSize.ExtraSmall}
|
||||
onClick={() => onDeleteData(bucket)}
|
||||
/>
|
||||
</FeatureFlag>
|
||||
</FlexBox>
|
||||
)
|
||||
}
|
||||
|
||||
const mdtp: DispatchProps = {
|
||||
onAddBucketLabel: addBucketLabel,
|
||||
onDeleteBucketLabel: deleteBucketLabel,
|
||||
onSetDataLoadersBucket: setBucketInfo,
|
||||
onSetDataLoadersType: setDataLoadersType,
|
||||
}
|
||||
|
||||
export default connect<{}, DispatchProps>(
|
||||
null,
|
||||
mdtp
|
||||
)(withRouter<Props>(BucketCardActions))
|
|
@ -0,0 +1,20 @@
|
|||
.copy-bucket-id {
|
||||
transition: color 0.25s ease;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
color: $c-pool;
|
||||
}
|
||||
}
|
||||
|
||||
.copy-bucket-id--helper {
|
||||
color: $g13-mist;
|
||||
transition: opacity 0.25s ease;
|
||||
opacity: 0;
|
||||
display: inline-block;
|
||||
margin-left: $cf-marg-b;
|
||||
}
|
||||
|
||||
.copy-bucket-id:hover .copy-bucket-id--helper {
|
||||
opacity: 1;
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
// Libraries
|
||||
import React, {FC} from 'react'
|
||||
import CopyToClipboard from 'react-copy-to-clipboard'
|
||||
import {capitalize} from 'lodash'
|
||||
import {connect} from 'react-redux'
|
||||
|
||||
// Constants
|
||||
import {
|
||||
copyToClipboardSuccess,
|
||||
copyToClipboardFailed,
|
||||
} from 'src/shared/copy/notifications'
|
||||
|
||||
// Actions
|
||||
import {notify as notifyAction} from 'src/shared/actions/notifications'
|
||||
|
||||
// Components
|
||||
import {ResourceCard} from '@influxdata/clockface'
|
||||
|
||||
// Types
|
||||
import {OwnBucket} from 'src/types'
|
||||
|
||||
interface DispatchProps {
|
||||
notify: typeof notifyAction
|
||||
}
|
||||
|
||||
interface OwnProps {
|
||||
bucket: OwnBucket
|
||||
}
|
||||
|
||||
type Props = OwnProps & DispatchProps
|
||||
|
||||
const BucketCardMeta: FC<Props> = ({bucket, notify}) => {
|
||||
const handleCopyAttempt = (
|
||||
copiedText: string,
|
||||
isSuccessful: boolean
|
||||
): void => {
|
||||
const text = copiedText.slice(0, 30).trimRight()
|
||||
const truncatedText = `${text}...`
|
||||
|
||||
if (isSuccessful) {
|
||||
notify(copyToClipboardSuccess(truncatedText, 'Bucket ID'))
|
||||
} else {
|
||||
notify(copyToClipboardFailed(truncatedText, 'Bucket ID'))
|
||||
}
|
||||
}
|
||||
|
||||
const persistentBucketMeta = (
|
||||
<span data-testid="bucket-retention">
|
||||
Retention: {capitalize(bucket.readableRetention)}
|
||||
</span>
|
||||
)
|
||||
|
||||
if (bucket.type === 'system') {
|
||||
return (
|
||||
<ResourceCard.Meta>
|
||||
<span
|
||||
className="system-bucket"
|
||||
key={`system-bucket-indicator-${bucket.id}`}
|
||||
>
|
||||
System Bucket
|
||||
</span>
|
||||
{persistentBucketMeta}
|
||||
</ResourceCard.Meta>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<ResourceCard.Meta>
|
||||
{persistentBucketMeta}
|
||||
<CopyToClipboard text={bucket.id} onCopy={handleCopyAttempt}>
|
||||
<span className="copy-bucket-id" title="Click to Copy to Clipboard">
|
||||
ID: {bucket.id}
|
||||
<span className="copy-bucket-id--helper">Copy to Clipboard</span>
|
||||
</span>
|
||||
</CopyToClipboard>
|
||||
</ResourceCard.Meta>
|
||||
)
|
||||
}
|
||||
|
||||
const mdtp: DispatchProps = {
|
||||
notify: notifyAction,
|
||||
}
|
||||
|
||||
export default connect<{}, DispatchProps, OwnProps>(
|
||||
null,
|
||||
mdtp
|
||||
)(BucketCardMeta)
|
|
@ -1,8 +1,15 @@
|
|||
// Libraries
|
||||
import React, {FC} from 'react'
|
||||
import {withRouter, WithRouterProps} from 'react-router'
|
||||
import CopyToClipboard from 'react-copy-to-clipboard'
|
||||
import {connect} from 'react-redux'
|
||||
|
||||
// Constants
|
||||
import {
|
||||
copyToClipboardSuccess,
|
||||
copyToClipboardFailed,
|
||||
} from 'src/shared/copy/notifications'
|
||||
|
||||
// Components
|
||||
import {
|
||||
ResourceCard,
|
||||
|
@ -20,12 +27,14 @@ import {Context} from 'src/clockface'
|
|||
|
||||
// Actions
|
||||
import {deleteDemoDataBucketMembership} from 'src/cloud/actions/demodata'
|
||||
import {notify as notifyAction} from 'src/shared/actions/notifications'
|
||||
|
||||
// Types
|
||||
import {DemoBucket} from 'src/types'
|
||||
|
||||
interface DispatchProps {
|
||||
removeBucket: typeof deleteDemoDataBucketMembership
|
||||
notify: typeof notifyAction
|
||||
}
|
||||
|
||||
interface Props {
|
||||
|
@ -37,11 +46,26 @@ const DemoDataBucketCard: FC<Props & WithRouterProps & DispatchProps> = ({
|
|||
router,
|
||||
params: {orgID},
|
||||
removeBucket,
|
||||
notify,
|
||||
}) => {
|
||||
const handleNameClick = () => {
|
||||
router.push(`/orgs/${orgID}/data-explorer?bucket=${bucket.name}`)
|
||||
}
|
||||
|
||||
const handleCopyAttempt = (
|
||||
copiedText: string,
|
||||
isSuccessful: boolean
|
||||
): void => {
|
||||
const text = copiedText.slice(0, 30).trimRight()
|
||||
const truncatedText = `${text}...`
|
||||
|
||||
if (isSuccessful) {
|
||||
notify(copyToClipboardSuccess(truncatedText, 'Bucket ID'))
|
||||
} else {
|
||||
notify(copyToClipboardFailed(truncatedText, 'Bucket ID'))
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<ResourceCard
|
||||
testID={`bucket-card ${bucket.name}`}
|
||||
|
@ -83,6 +107,12 @@ const DemoDataBucketCard: FC<Props & WithRouterProps & DispatchProps> = ({
|
|||
Demo Data Bucket
|
||||
</span>
|
||||
<>Retention: {bucket.readableRetention}</>
|
||||
<CopyToClipboard text={bucket.id} onCopy={handleCopyAttempt}>
|
||||
<span className="copy-bucket-id" title="Click to Copy to Clipboard">
|
||||
ID: {bucket.id}
|
||||
<span className="copy-bucket-id--helper">Copy to Clipboard</span>
|
||||
</span>
|
||||
</CopyToClipboard>
|
||||
</ResourceCard.Meta>
|
||||
<FlexBox
|
||||
direction={FlexDirection.Row}
|
||||
|
@ -105,6 +135,7 @@ const DemoDataBucketCard: FC<Props & WithRouterProps & DispatchProps> = ({
|
|||
|
||||
const mdtp: DispatchProps = {
|
||||
removeBucket: deleteDemoDataBucketMembership,
|
||||
notify: notifyAction,
|
||||
}
|
||||
|
||||
export default connect<{}, DispatchProps, {}>(
|
||||
|
|
|
@ -123,6 +123,7 @@
|
|||
@import 'src/dashboards/components/DashboardsCardGrid.scss';
|
||||
@import 'src/dashboards/components/DashboardLightMode.scss';
|
||||
@import 'src/buckets/components/DemoDataDropdown.scss';
|
||||
@import 'src/buckets/components/BucketCardMeta.scss';
|
||||
@import 'src/shared/components/notifications/Notification.scss';
|
||||
|
||||
// External
|
||||
|
|
Loading…
Reference in New Issue